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
)