@marcel91200 / caddy Mirror · read-only / commits / 8ec51bbe
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

metrics: Initial integration of Prometheus metrics (#3709)

Signed-off-by: Dave Henderson <dhenderson@gmail.com>

Dave Henderson committed Sep 17, 2020 at 14:01 UTC 8ec51bbede59e1fa6ac24de3607f6e7bb3e3a654
12 files changed +518 -33
admin.go
+33 -12
@@ -35,6 +35,8 @@ import (
35 "sync"
36 "time"
37
38 + "github.com/prometheus/client_golang/prometheus"
39 + "github.com/prometheus/client_golang/prometheus/promhttp"
40 "go.uber.org/zap"
41 )
42
@@ -107,34 +109,53 @@ func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
109 mux: http.NewServeMux(),
110 }
111
112 + addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) {
113 + labels := prometheus.Labels{"path": pattern, "handler": handlerLabel}
114 + h = promhttp.InstrumentHandlerCounter(
115 + adminMetrics.requestCount.MustCurryWith(labels),
116 + h,
117 + )
118 + muxWrap.mux.Handle(pattern, h)
119 + }
120 // addRoute just calls muxWrap.mux.Handle after
121 // wrapping the handler with error handling
112 - addRoute := func(pattern string, h AdminHandler) {
122 + addRoute := func(pattern string, handlerLabel string, h AdminHandler) {
123 wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124 err := h.ServeHTTP(w, r)
125 + if err != nil {
126 + labels := prometheus.Labels{
127 + "path": pattern,
128 + "handler": handlerLabel,
129 + "method": r.Method,
130 + }
131 + adminMetrics.requestErrors.With(labels).Inc()
132 + }
133 muxWrap.handleError(w, r, err)
134 })
117 - muxWrap.mux.Handle(pattern, wrapper)
135 + addRouteWithMetrics(pattern, handlerLabel, wrapper)
136 }
137
138 + const handlerLabel = "admin"
139 +
140 // register standard config control endpoints
121 - addRoute("/"+rawConfigKey+"/", AdminHandlerFunc(handleConfig))
122 - addRoute("/id/", AdminHandlerFunc(handleConfigID))
123 - addRoute("/stop", AdminHandlerFunc(handleStop))
141 + addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
142 + addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
143 + addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop))
144
145 // register debugging endpoints
126 - muxWrap.mux.HandleFunc("/debug/pprof/", pprof.Index)
127 - muxWrap.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
128 - muxWrap.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
129 - muxWrap.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
130 - muxWrap.mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
131 - muxWrap.mux.Handle("/debug/vars", expvar.Handler())
146 + addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
147 + addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
148 + addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
149 + addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
150 + addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
151 + addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler())
152
153 // register third-party module endpoints
154 for _, m := range GetModules("admin.api") {
155 router := m.New().(AdminRouter)
156 + handlerLabel := m.ID.Name()
157 for _, route := range router.Routes() {
137 - addRoute(route.Pattern, route.Handler)
158 + addRoute(route.Pattern, handlerLabel, route.Handler)
159 }
160 }
161
caddyconfig/httpcaddyfile/directives.go
+1
@@ -63,6 +63,7 @@ var directiveOrder = []string{
63
64 // handlers that typically respond to requests
65 "respond",
66 + "metrics",
67 "reverse_proxy",
68 "php_fastcgi",
69 "file_server",
go.mod
+1
@@ -17,6 +17,7 @@ require (
17 github.com/mholt/acmez v0.1.1-0.20200811184240-dc9c5f05ed1e
18 github.com/naoina/go-stringutil v0.1.0 // indirect
19 github.com/naoina/toml v0.1.1
20 + github.com/prometheus/client_golang v1.7.1
21 github.com/smallstep/certificates v0.15.1
22 github.com/smallstep/cli v0.15.0
23 github.com/smallstep/nosql v0.3.0
go.sum
+14 -18
@@ -74,6 +74,7 @@ github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P
74 github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
75 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
76 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
77 +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
78 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
79 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
80 github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
@@ -85,6 +86,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA
86 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
87 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
88 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
89 +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
90 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
91 github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
92 github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
@@ -140,9 +142,7 @@ github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
142 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
143 github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8=
144 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
143 -github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
145 github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
145 -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
146 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
147 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
148 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -196,7 +196,6 @@ github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
196 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
197 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
198 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
199 -github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
199 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
200 github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
201 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
@@ -209,9 +208,7 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi
208 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
209 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
210 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
212 -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
211 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
214 -github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
212 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
213 github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
214 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
@@ -291,7 +288,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
288 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
289 github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
290 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
294 -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
291 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
292 github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
293 github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@@ -309,6 +305,7 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22
305 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
306 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
307 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
308 +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
309 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
310 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
311 github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
@@ -380,6 +377,7 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
377 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
378 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
379 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
380 +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
381 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
382 github.com/mholt/acmez v0.1.1-0.20200811184240-dc9c5f05ed1e h1:F5E9CM0APrZtTfowJfvUBmrCjuHLfslZyFlNTEB7UV4=
383 github.com/mholt/acmez v0.1.1-0.20200811184240-dc9c5f05ed1e/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
@@ -425,7 +423,6 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW
423 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
424 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
425 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
428 -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
426 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
427 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
428 github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
@@ -434,7 +431,6 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV
431 github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
432 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
433 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
437 -github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
434 github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
435 github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
436 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
@@ -458,20 +454,28 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
454 github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
455 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
456 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
457 +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
458 +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
459 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
460 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
461 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
462 github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
463 +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
464 +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
465 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
466 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
467 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
468 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
469 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
470 +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
471 +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
472 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
473 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
474 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
475 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
476 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
477 +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
478 +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
479 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
480 github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
481 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -527,11 +531,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
531 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
532 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
533 github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
530 -github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15 h1:kSImCuenAkXtCaBeQ1UhmzzJGRhSm8sVH7I3sHE2Qdg=
534 github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
535 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
536 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
534 -github.com/smallstep/certificates v0.15.0 h1:riIt3fBWuenlvHoqkVuUOxQbhtxFQMO1ziSr7usb1J0=
537 github.com/smallstep/certificates v0.15.0/go.mod h1:awyVXYIVn4J9ANSSnDuA3FjKia7+QixaqorGW8CKGq8=
538 github.com/smallstep/certificates v0.15.1 h1:Es2IGgU4D0RE4OKys/VB1KvAuGuZwkxR7D9A0DeM/Ow=
539 github.com/smallstep/certificates v0.15.1/go.mod h1:hDmNejfuVTDDe1NBIKrhL9xjELRJb2ZFM+bDXwSW9xI=
@@ -627,7 +629,6 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
629 go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
630 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
631 go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U=
630 -go.step.sm/crypto v0.1.1 h1:xg3kUS30hEnwgbxtKwq9a4MJaeiU616HSug60LU9B2E=
632 go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8=
633 go.step.sm/crypto v0.2.0 h1:Rx+XqrNO4ZGHWlT9QCXls2L9pvcNiI23zEpAq0fctvY=
634 go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww=
@@ -769,10 +770,10 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
770 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
771 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
772 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
772 -golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
773 golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
774 -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
774 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
775 +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
776 +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
777 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
778 golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
779 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -864,9 +865,7 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
865 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
866 google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
867 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
867 -google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
868 google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
869 -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
869 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
870 google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
871 google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -889,9 +888,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
888 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
889 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
890 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
892 -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
891 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
894 -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc h1:TnonUr8u3himcMY0vSh23jFOXA+cnucl1gB6EQTReBI=
892 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
893 google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
894 google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
@@ -903,7 +900,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
900 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
901 gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
902 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
906 -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
903 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
904 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
905 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
metrics.go new
+32
@@ -0,0 +1,32 @@
1 +package caddy
2 +
3 +import (
4 + "github.com/prometheus/client_golang/prometheus"
5 + "github.com/prometheus/client_golang/prometheus/promauto"
6 +)
7 +
8 +// define and register the metrics used in this package.
9 +func init() {
10 + prometheus.MustRegister(prometheus.NewBuildInfoCollector())
11 +
12 + const ns, sub = "caddy", "admin"
13 +
14 + adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
15 + Namespace: ns,
16 + Subsystem: sub,
17 + Name: "http_requests_total",
18 + Help: "Counter of requests made to the Admin API's HTTP endpoints.",
19 + }, []string{"handler", "path", "code", "method"})
20 + adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{
21 + Namespace: ns,
22 + Subsystem: sub,
23 + Name: "http_request_errors_total",
24 + Help: "Number of requests resulting in middleware errors.",
25 + }, []string{"handler", "path", "method"})
26 +}
27 +
28 +// adminMetrics is a collection of metrics that can be tracked for the admin API.
29 +var adminMetrics = struct {
30 + requestCount *prometheus.CounterVec
31 + requestErrors *prometheus.CounterVec
32 +}{}
modules/caddyhttp/app.go
+2
@@ -226,6 +226,8 @@ func (app *App) Provision(ctx caddy.Context) error {
226 // route handler so that important security checks are done, etc.
227 primaryRoute := emptyHandler
228 if srv.Routes != nil {
229 + // inject the server name for observability purposes
230 + ctx.Context = contextWithServerName(ctx.Context, srvName)
231 err := srv.Routes.ProvisionHandlers(ctx)
232 if err != nil {
233 return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
modules/caddyhttp/metrics.go new
+195
@@ -0,0 +1,195 @@
1 +package caddyhttp
2 +
3 +import (
4 + "context"
5 + "net/http"
6 + "strconv"
7 + "sync"
8 + "time"
9 +
10 + "github.com/prometheus/client_golang/prometheus"
11 + "github.com/prometheus/client_golang/prometheus/promauto"
12 +)
13 +
14 +var httpMetrics = struct {
15 + init sync.Once
16 + requestInFlight *prometheus.GaugeVec
17 + requestCount *prometheus.CounterVec
18 + requestErrors *prometheus.CounterVec
19 + requestDuration *prometheus.HistogramVec
20 + requestSize *prometheus.HistogramVec
21 + responseSize *prometheus.HistogramVec
22 + responseDuration *prometheus.HistogramVec
23 +}{
24 + init: sync.Once{},
25 +}
26 +
27 +func initHTTPMetrics() {
28 + const ns, sub = "caddy", "http"
29 +
30 + basicLabels := []string{"server", "handler"}
31 + httpMetrics.requestInFlight = promauto.NewGaugeVec(prometheus.GaugeOpts{
32 + Namespace: ns,
33 + Subsystem: sub,
34 + Name: "requests_in_flight",
35 + Help: "Number of requests currently handled by this server.",
36 + }, basicLabels)
37 + httpMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{
38 + Namespace: ns,
39 + Subsystem: sub,
40 + Name: "request_errors_total",
41 + Help: "Number of requests resulting in middleware errors.",
42 + }, basicLabels)
43 + httpMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
44 + Namespace: ns,
45 + Subsystem: sub,
46 + Name: "requests_total",
47 + Help: "Counter of HTTP(S) requests made.",
48 + }, basicLabels)
49 +
50 + // TODO: allow these to be customized in the config
51 + durationBuckets := prometheus.DefBuckets
52 + sizeBuckets := prometheus.ExponentialBuckets(256, 4, 8)
53 +
54 + httpLabels := []string{"server", "handler", "code", "method"}
55 + httpMetrics.requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
56 + Namespace: ns,
57 + Subsystem: sub,
58 + Name: "request_duration_seconds",
59 + Help: "Histogram of round-trip request durations.",
60 + Buckets: durationBuckets,
61 + }, httpLabels)
62 + httpMetrics.requestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
63 + Namespace: ns,
64 + Subsystem: sub,
65 + Name: "request_size_bytes",
66 + Help: "Total size of the request. Includes body",
67 + Buckets: sizeBuckets,
68 + }, httpLabels)
69 + httpMetrics.responseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
70 + Namespace: ns,
71 + Subsystem: sub,
72 + Name: "response_size_bytes",
73 + Help: "Size of the returned response.",
74 + Buckets: sizeBuckets,
75 + }, httpLabels)
76 + httpMetrics.responseDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
77 + Namespace: ns,
78 + Subsystem: sub,
79 + Name: "response_duration_seconds",
80 + Help: "Histogram of times to first byte in response bodies.",
81 + Buckets: durationBuckets,
82 + }, httpLabels)
83 +}
84 +
85 +type ctxKeyServerName struct{}
86 +
87 +// serverNameFromContext extracts the current server name from the context.
88 +// Returns "UNKNOWN" if none is available (should probably never happen?)
89 +func serverNameFromContext(ctx context.Context) string {
90 + srvName, ok := ctx.Value(ctxKeyServerName{}).(string)
91 + if !ok {
92 + return "UNKNOWN"
93 + }
94 + return srvName
95 +}
96 +
97 +func contextWithServerName(ctx context.Context, serverName string) context.Context {
98 + return context.WithValue(ctx, ctxKeyServerName{}, serverName)
99 +}
100 +
101 +type metricsInstrumentedHandler struct {
102 + labels prometheus.Labels
103 + statusLabels prometheus.Labels
104 + mh MiddlewareHandler
105 +}
106 +
107 +func newMetricsInstrumentedHandler(server, handler string, mh MiddlewareHandler) *metricsInstrumentedHandler {
108 + httpMetrics.init.Do(func() {
109 + initHTTPMetrics()
110 + })
111 +
112 + labels := prometheus.Labels{"server": server, "handler": handler}
113 + statusLabels := prometheus.Labels{"server": server, "handler": handler, "code": "", "method": ""}
114 + return &metricsInstrumentedHandler{labels, statusLabels, mh}
115 +}
116 +
117 +func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
118 + inFlight := httpMetrics.requestInFlight.With(h.labels)
119 + inFlight.Inc()
120 + defer inFlight.Dec()
121 +
122 + statusLabels := prometheus.Labels{"method": r.Method}
123 + for k, v := range h.labels {
124 + statusLabels[k] = v
125 + }
126 +
127 + start := time.Now()
128 +
129 + // This is a _bit_ of a hack - it depends on the ShouldBufferFunc always
130 + // being called when the headers are written.
131 + // Effectively the same behaviour as promhttp.InstrumentHandlerTimeToWriteHeader.
132 + writeHeaderRecorder := ShouldBufferFunc(func(status int, header http.Header) bool {
133 + statusLabels["code"] = sanitizeCode(status)
134 + ttfb := time.Since(start).Seconds()
135 + observeWithExemplar(statusLabels, httpMetrics.responseDuration, ttfb)
136 + return false
137 + })
138 + wrec := NewResponseRecorder(w, nil, writeHeaderRecorder)
139 + err := h.mh.ServeHTTP(wrec, r, next)
140 + dur := time.Since(start).Seconds()
141 + httpMetrics.requestCount.With(h.labels).Inc()
142 + if err != nil {
143 + httpMetrics.requestErrors.With(h.labels).Inc()
144 + return err
145 + }
146 +
147 + observeWithExemplar(statusLabels, httpMetrics.requestDuration, dur)
148 + observeWithExemplar(statusLabels, httpMetrics.requestSize, float64(computeApproximateRequestSize(r)))
149 + httpMetrics.responseSize.With(statusLabels).Observe(float64(wrec.Size()))
150 +
151 + return nil
152 +}
153 +
154 +func observeWithExemplar(l prometheus.Labels, o *prometheus.HistogramVec, value float64) {
155 + obs := o.With(l)
156 + if oe, ok := obs.(prometheus.ExemplarObserver); ok {
157 + oe.ObserveWithExemplar(value, l)
158 + return
159 + }
160 + // _should_ be a noop, but here just in case...
161 + obs.Observe(value)
162 +}
163 +
164 +func sanitizeCode(code int) string {
165 + if code == 0 {
166 + return "200"
167 + }
168 + return strconv.Itoa(code)
169 +
170 +}
171 +
172 +// taken from https://github.com/prometheus/client_golang/blob/6007b2b5cae01203111de55f753e76d8dac1f529/prometheus/promhttp/instrument_server.go#L298
173 +func computeApproximateRequestSize(r *http.Request) int {
174 + s := 0
175 + if r.URL != nil {
176 + s += len(r.URL.String())
177 + }
178 +
179 + s += len(r.Method)
180 + s += len(r.Proto)
181 + for name, values := range r.Header {
182 + s += len(name)
183 + for _, value := range values {
184 + s += len(value)
185 + }
186 + }
187 + s += len(r.Host)
188 +
189 + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
190 +
191 + if r.ContentLength != -1 {
192 + s += int(r.ContentLength)
193 + }
194 + return s
195 +}
modules/caddyhttp/metrics_test.go new
+66
@@ -0,0 +1,66 @@
1 +package caddyhttp
2 +
3 +import (
4 + "context"
5 + "errors"
6 + "net/http"
7 + "net/http/httptest"
8 + "testing"
9 +
10 + "github.com/prometheus/client_golang/prometheus/testutil"
11 +)
12 +
13 +func TestServerNameFromContext(t *testing.T) {
14 + ctx := context.Background()
15 + expected := "UNKNOWN"
16 + if actual := serverNameFromContext(ctx); actual != expected {
17 + t.Errorf("Not equal: expected %q, but got %q", expected, actual)
18 + }
19 +
20 + in := "foo"
21 + ctx = contextWithServerName(ctx, in)
22 + if actual := serverNameFromContext(ctx); actual != in {
23 + t.Errorf("Not equal: expected %q, but got %q", in, actual)
24 + }
25 +}
26 +
27 +func TestMetricsInstrumentedHandler(t *testing.T) {
28 + handlerErr := errors.New("oh noes")
29 + response := []byte("hello world!")
30 + h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
31 + if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 1.0 {
32 + t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual)
33 + }
34 + if handlerErr == nil {
35 + w.Write(response)
36 + }
37 + return handlerErr
38 + })
39 +
40 + mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
41 + return h.ServeHTTP(w, r)
42 + })
43 +
44 + ih := newMetricsInstrumentedHandler("foo", "bar", mh)
45 +
46 + r := httptest.NewRequest("GET", "/", nil)
47 + w := httptest.NewRecorder()
48 +
49 + if actual := ih.ServeHTTP(w, r, h); actual != handlerErr {
50 + t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual)
51 + }
52 + if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 0.0 {
53 + t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual)
54 + }
55 +
56 + handlerErr = nil
57 + if err := ih.ServeHTTP(w, r, h); err != nil {
58 + t.Errorf("Received unexpected error: %w", err)
59 + }
60 +}
61 +
62 +type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error
63 +
64 +func (f middlewareHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, h Handler) error {
65 + return f(w, r, h)
66 +}
modules/caddyhttp/routes.go
+9 -3
@@ -157,7 +157,7 @@ func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error {
157
158 // pre-compile the middleware handler chain
159 for _, midhandler := range routes[i].Handlers {
160 - routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler))
160 + routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler))
161 }
162 }
163 return nil
@@ -242,7 +242,13 @@ func wrapRoute(route Route) Middleware {
242 // we need to pull this particular MiddlewareHandler
243 // pointer into its own stack frame to preserve it so it
244 // won't be overwritten in future loop iterations.
245 -func wrapMiddleware(mh MiddlewareHandler) Middleware {
245 +func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
246 + // first, wrap the middleware with metrics instrumentation
247 + metricsHandler := newMetricsInstrumentedHandler(
248 + serverNameFromContext(ctx.Context),
249 + caddy.GetModuleName(mh),
250 + mh,
251 + )
252 return func(next Handler) Handler {
253 // copy the next handler (it's an interface, so it's
254 // just a very lightweight copy of a pointer); this
@@ -253,7 +259,7 @@ func wrapMiddleware(mh MiddlewareHandler) Middleware {
259 return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
260 // TODO: This is where request tracing could be implemented
261 // TODO: see what the std lib gives us in terms of stack tracing too
256 - return mh.ServeHTTP(w, r, nextCopy)
262 + return metricsHandler.ServeHTTP(w, r, nextCopy)
263 })
264 }
265 }
modules/metrics/adminmetrics.go new
+55
@@ -0,0 +1,55 @@
1 +// Copyright 2020 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package metrics
16 +
17 +import (
18 + "net/http"
19 +
20 + "github.com/caddyserver/caddy/v2"
21 +)
22 +
23 +func init() {
24 + caddy.RegisterModule(AdminMetrics{})
25 +}
26 +
27 +// AdminMetrics is a module that serves a metrics endpoint so that any gathered
28 +// metrics can be exposed for scraping. This module is not configurable, and
29 +// is permanently mounted to the admin API endpoint at "/metrics".
30 +// See the Metrics module for a configurable endpoint that is usable if the
31 +// Admin API is disabled.
32 +type AdminMetrics struct{}
33 +
34 +// CaddyModule returns the Caddy module information.
35 +func (AdminMetrics) CaddyModule() caddy.ModuleInfo {
36 + return caddy.ModuleInfo{
37 + ID: "admin.api.metrics",
38 + New: func() caddy.Module { return new(AdminMetrics) },
39 + }
40 +}
41 +
42 +// Routes returns a route for the /metrics endpoint.
43 +func (m *AdminMetrics) Routes() []caddy.AdminRoute {
44 + metricsHandler := createMetricsHandler(nil)
45 + h := caddy.AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
46 + metricsHandler.ServeHTTP(w, r)
47 + return nil
48 + })
49 + return []caddy.AdminRoute{{Pattern: "/metrics", Handler: h}}
50 +}
51 +
52 +// Interface guards
53 +var (
54 + _ caddy.AdminRouter = (*AdminMetrics)(nil)
55 +)
modules/metrics/metrics.go new
+109
@@ -0,0 +1,109 @@
1 +// Copyright 2020 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package metrics
16 +
17 +import (
18 + "net/http"
19 +
20 + "github.com/caddyserver/caddy/v2"
21 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
22 + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
23 + "github.com/caddyserver/caddy/v2/modules/caddyhttp"
24 +
25 + "go.uber.org/zap"
26 +
27 + "github.com/prometheus/client_golang/prometheus"
28 + "github.com/prometheus/client_golang/prometheus/promhttp"
29 +)
30 +
31 +func init() {
32 + caddy.RegisterModule(Metrics{})
33 + httpcaddyfile.RegisterHandlerDirective("metrics", parseCaddyfile)
34 +}
35 +
36 +// Metrics is a module that serves a /metrics endpoint so that any gathered
37 +// metrics can be exposed for scraping. This module is configurable by end-users
38 +// unlike AdminMetrics.
39 +type Metrics struct {
40 + metricsHandler http.Handler
41 +}
42 +
43 +// CaddyModule returns the Caddy module information.
44 +func (Metrics) CaddyModule() caddy.ModuleInfo {
45 + return caddy.ModuleInfo{
46 + ID: "http.handlers.metrics",
47 + New: func() caddy.Module { return new(Metrics) },
48 + }
49 +}
50 +
51 +type zapLogger struct {
52 + zl *zap.Logger
53 +}
54 +
55 +func (l *zapLogger) Println(v ...interface{}) {
56 + l.zl.Sugar().Error(v...)
57 +}
58 +
59 +// Provision sets up m.
60 +func (m *Metrics) Provision(ctx caddy.Context) error {
61 + log := ctx.Logger(m)
62 + m.metricsHandler = createMetricsHandler(&zapLogger{log})
63 + return nil
64 +}
65 +
66 +func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
67 + var m Metrics
68 + err := m.UnmarshalCaddyfile(h.Dispenser)
69 + return m, err
70 +}
71 +
72 +// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
73 +//
74 +// metrics <matcher>
75 +//
76 +func (m *Metrics) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
77 + for d.Next() {
78 + args := d.RemainingArgs()
79 + if len(args) > 0 {
80 + return d.ArgErr()
81 + }
82 + }
83 + return nil
84 +}
85 +
86 +func (m Metrics) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
87 + m.metricsHandler.ServeHTTP(w, r)
88 + return next.ServeHTTP(w, r)
89 +}
90 +
91 +// Interface guards
92 +var (
93 + _ caddy.Provisioner = (*Metrics)(nil)
94 + _ caddyhttp.MiddlewareHandler = (*Metrics)(nil)
95 + _ caddyfile.Unmarshaler = (*Metrics)(nil)
96 +)
97 +
98 +func createMetricsHandler(logger promhttp.Logger) http.Handler {
99 + return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer,
100 + promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
101 + // will only log errors if logger is non-nil
102 + ErrorLog: logger,
103 +
104 + // Allow OpenMetrics format to be negotiated - largely compatible,
105 + // except quantile/le label values always have a decimal.
106 + EnableOpenMetrics: true,
107 + }),
108 + )
109 +}
modules/standard/imports.go
+1
@@ -11,4 +11,5 @@ import (
11 _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek"
12 _ "github.com/caddyserver/caddy/v2/modules/filestorage"
13 _ "github.com/caddyserver/caddy/v2/modules/logging"
14 + _ "github.com/caddyserver/caddy/v2/modules/metrics"
15 )