Skip to content

Commit 186007a

Browse files
author
Mike Holloway
committed
patch caddy-prometheus plugin - miekg/caddy-prometheus#43
1 parent a06fed8 commit 186007a

5 files changed

Lines changed: 227 additions & 1 deletion

File tree

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ RUN git clone https://github.com/lucas-clemente/aes12 /go/src/github.com/lucas-c
4949
RUN git clone https://github.com/lucas-clemente/quic-go-certificates /go/src/github.com/lucas-clemente/quic-go-certificates
5050
RUN rm -rf /go/src/github.com/lucas-clemente/quic-go && git clone --single-branch --branch v0.11.2 https://github.com/lucas-clemente/quic-go /go/src/github.com/lucas-clemente/quic-go
5151

52+
# Deal with https://github.com/miekg/caddy-prometheus/issues/43
53+
COPY patches/handler.go /go/src/github.com/miekg/caddy-prometheus/handler.go
54+
5255
# build with telemetry enabled
5356
RUN cd /go/src/github.com/caddyserver/caddy/caddy \
5457
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go/bin/caddy

Dockerfile-no-stats

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ RUN git clone https://github.com/lucas-clemente/aes12 /go/src/github.com/lucas-c
4949
RUN git clone https://github.com/lucas-clemente/quic-go-certificates /go/src/github.com/lucas-clemente/quic-go-certificates
5050
RUN rm -rf /go/src/github.com/lucas-clemente/quic-go && git clone --single-branch --branch v0.11.2 https://github.com/lucas-clemente/quic-go /go/src/github.com/lucas-clemente/quic-go
5151

52+
# Deal with https://github.com/miekg/caddy-prometheus/issues/43
53+
COPY patches/handler.go /go/src/github.com/miekg/caddy-prometheus/handler.go
54+
5255
# build with telemetry disabled
5356
RUN cd /go/src/github.com/caddyserver/caddy/caddy \
5457
&& sed -i 's/Telemetry = true/Telemetry = false/' /go/src/github.com/caddyserver/caddy/caddy/caddymain/run.go \

Dockerfile.debug

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#
2+
# Build stage by @abiosoft https://github.com/abiosoft/caddy-docker
3+
#
4+
FROM golang:1.12-alpine as build
5+
6+
ARG BUILD_DATE
7+
ARG VCS_REF
8+
ARG DEBIAN_FRONTEND=noninteractive
9+
10+
ARG caddy_version="v1.0.1"
11+
ARG plugins="cache,expires,git,jwt,prometheus,realip,reauth"
12+
13+
RUN apk add --no-cache --no-progress git ca-certificates
14+
15+
# caddy
16+
RUN git clone https://github.com/caddyserver/caddy -b "${caddy_version}" /go/src/github.com/caddyserver/caddy \
17+
&& cd /go/src/github.com/caddyserver/caddy \
18+
&& git checkout -b "${caddy_version}"
19+
20+
21+
# plugin helper
22+
RUN go get -v github.com/abiosoft/caddyplug/caddyplug
23+
RUN sed -i 's/mholt\/caddy"/caddyserver\/caddy"/g' /go/bin/caddyplug
24+
25+
# plugins
26+
RUN for plugin in $(echo $plugins | tr "," " "); do \
27+
go get -v $(caddyplug package $plugin); \
28+
printf "package caddyhttp\nimport _ \"$(caddyplug package $plugin)\"" > \
29+
/go/src/github.com/caddyserver/caddy/caddyhttp/$plugin.go ; \
30+
done
31+
32+
# https://github.com/coredns/coredns/issues/2959
33+
RUN find /go/src/github.com/ -name '*.go' | while read -r f; do \
34+
sed -i.bak 's/\/mholt\/caddy/\/caddyserver\/caddy/g' $f && rm $f.bak ; \
35+
done
36+
37+
# builder dependency
38+
RUN git clone https://github.com/dustin/go-humanize /go/src/github.com/dustin/go-humanize
39+
RUN git clone https://github.com/gorilla/websocket /go/src/github.com/gorilla/websocket
40+
RUN git clone https://github.com/jimstudt/http-authentication /go/src/github.com/jimstudt/http-authentication
41+
RUN git clone https://github.com/naoina/toml /go/src/github.com/naoina/toml
42+
RUN git clone https://github.com/naoina/go-stringutil /go/src/github.com/naoina/go-stringutil
43+
RUN git clone https://github.com/VividCortex/ewma /go/src/github.com/VividCortex/ewma
44+
RUN git clone https://github.com/marten-seemann/qpack /go/src/github.com/marten-seemann/qpack
45+
RUN git clone https://github.com/cheekybits/genny /go/src/github.com/cheekybits/genny
46+
RUN git clone --single-branch --branch v0.2.3 https://github.com/marten-seemann/qtls /go/src/github.com/marten-seemann/qtls
47+
RUN git clone https://github.com/bifurcation/mint /go/src/github.com/bifurcation/mint
48+
RUN git clone https://github.com/lucas-clemente/aes12 /go/src/github.com/lucas-clemente/aes12
49+
RUN git clone https://github.com/lucas-clemente/quic-go-certificates /go/src/github.com/lucas-clemente/quic-go-certificates
50+
RUN rm -rf /go/src/github.com/lucas-clemente/quic-go && git clone --single-branch --branch v0.11.2 https://github.com/lucas-clemente/quic-go /go/src/github.com/lucas-clemente/quic-go
51+
52+
# Deal with https://github.com/miekg/caddy-prometheus/issues/43
53+
COPY patches/handler.go /go/src/github.com/miekg/caddy-prometheus/handler.go
54+
55+
# build with telemetry enabled
56+
RUN cd /go/src/github.com/caddyserver/caddy/caddy \
57+
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /go/bin/caddy
58+
59+
# test
60+
RUN /go/bin/caddy -version
61+
RUN /go/bin/caddy -plugins
62+
# copy caddy binary and ca certs
63+
RUN cp /go/bin/caddy /bin/caddy
64+
65+
# copy default caddyfile
66+
COPY Caddyfile /etc/Caddyfile
67+
68+
# set default path for certs
69+
VOLUME ["/etc/caddycerts"]
70+
ENV CADDYPATH=/etc/caddycerts
71+
72+
# serve from /www
73+
VOLUME ["/www"]
74+
WORKDIR /www
75+
COPY index.html /www/index.html
76+
77+
CMD ["/bin/bash"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# [caddy](https://github.com/mholt/caddy/)
33

4-
A Caddy image with and without "Telemetry" (stats reporting) enabled
4+
A Caddy image with and without telemetry enabled
55

66
# Usage
77

patches/handler.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package metrics
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"strconv"
7+
"strings"
8+
"time"
9+
10+
"github.com/caddyserver/caddy/caddyhttp/httpserver"
11+
)
12+
13+
func (m *Metrics) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
14+
next := m.next
15+
16+
hostname := m.hostname
17+
18+
if hostname == "" {
19+
originalHostname, err := host(r)
20+
if err != nil {
21+
hostname = "-"
22+
} else {
23+
hostname = originalHostname
24+
}
25+
}
26+
start := time.Now()
27+
28+
// Record response to get status code and size of the reply.
29+
rw := httpserver.NewResponseRecorder(w)
30+
// Get time to first write.
31+
tw := newTimedResponseWriter(rw)
32+
33+
status, err := next.ServeHTTP(tw, r)
34+
35+
// If nothing was explicitly written, consider the request written to
36+
// now that it has completed.
37+
tw.didWrite()
38+
39+
// Transparently capture the status code so as to not side effect other plugins
40+
stat := status
41+
if err != nil && status == 0 {
42+
// Some middlewares set the status to 0, but return an non nil error: map these to status 500
43+
stat = 500
44+
} else if status == 0 {
45+
// 'proxy' returns a status code of 0, but the actual status is available on rw.
46+
// Note that if 'proxy' encounters an error, it returns the appropriate status code (such as 502)
47+
// from ServeHTTP and is captured above with 'stat := status'.
48+
stat = rw.Status()
49+
}
50+
51+
fam := "1"
52+
if isIPv6(r.RemoteAddr) {
53+
fam = "2"
54+
}
55+
56+
proto := strconv.Itoa(r.ProtoMajor)
57+
proto = proto + "." + strconv.Itoa(r.ProtoMinor)
58+
59+
statusStr := strconv.Itoa(stat)
60+
61+
requestCount.WithLabelValues(hostname, fam, proto).Inc()
62+
requestDuration.WithLabelValues(hostname, fam, proto).Observe(time.Since(start).Seconds())
63+
responseSize.WithLabelValues(hostname, fam, proto, statusStr).Observe(float64(rw.Size()))
64+
responseStatus.WithLabelValues(hostname, fam, proto, statusStr).Inc()
65+
responseLatency.WithLabelValues(hostname, fam, proto, statusStr).Observe(tw.firstWrite().Sub(start).Seconds())
66+
67+
return status, err
68+
}
69+
70+
func host(r *http.Request) (string, error) {
71+
host, _, err := net.SplitHostPort(r.Host)
72+
if err != nil {
73+
if !strings.Contains(r.Host, ":") {
74+
return strings.ToLower(r.Host), nil
75+
}
76+
return "", err
77+
}
78+
return strings.ToLower(host), nil
79+
}
80+
81+
func isIPv6(addr string) bool {
82+
if host, _, err := net.SplitHostPort(addr); err == nil {
83+
// Strip away the port.
84+
addr = host
85+
}
86+
ip := net.ParseIP(addr)
87+
return ip != nil && ip.To4() == nil
88+
}
89+
90+
// A responseWriterWithFirstWrite is an http.Responsewrite with the ability to
91+
// tracks the time when the first response write happened.
92+
type responseWriterWithFirstWrite interface {
93+
http.ResponseWriter
94+
didWrite()
95+
firstWrite() time.Time
96+
}
97+
98+
// A timedResponseWriter tracks the time when the first response write
99+
// happened.
100+
type timedResponseWriter struct {
101+
firstWriteTime time.Time
102+
http.ResponseWriter
103+
}
104+
105+
// A timedResponseWriterHijacker is a timedResponseWriter and http.Hijacker.
106+
type timedResponseWriterHijacker struct {
107+
*timedResponseWriter
108+
http.Hijacker
109+
}
110+
111+
// NewLoggedResponseWriter wraps the provided http.ResponseWriter with Status
112+
// preserving the support to hijack the connection if supported by the provided
113+
// http.ResponseWriter.
114+
func newTimedResponseWriter(w http.ResponseWriter) responseWriterWithFirstWrite {
115+
tw := &timedResponseWriter{ResponseWriter: w}
116+
if hj, ok := w.(http.Hijacker); ok {
117+
return &timedResponseWriterHijacker{timedResponseWriter: tw, Hijacker: hj}
118+
}
119+
120+
return tw
121+
}
122+
123+
func (w *timedResponseWriter) didWrite() {
124+
if w.firstWriteTime.IsZero() {
125+
w.firstWriteTime = time.Now()
126+
}
127+
}
128+
129+
func (w *timedResponseWriter) firstWrite() time.Time {
130+
return w.firstWriteTime
131+
}
132+
133+
func (w *timedResponseWriter) Write(data []byte) (int, error) {
134+
w.didWrite()
135+
return w.ResponseWriter.Write(data)
136+
}
137+
138+
func (w *timedResponseWriter) WriteHeader(statuscode int) {
139+
// We consider this a write as it's valid to respond to a request by
140+
// just setting a status code and returning.
141+
w.didWrite()
142+
w.ResponseWriter.WriteHeader(statuscode)
143+
}

0 commit comments

Comments
 (0)