-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Add custom labels support for metrics #7207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
metrics { | ||
labels { | ||
proto "{http.request.proto}" | ||
method "{http.request.method}" | ||
client_ip "{http.request.remote}" | ||
host "{http.request.host}" | ||
} | ||
} | ||
} | ||
|
||
:8080 { | ||
respond "Hello World" 200 | ||
} | ||
---------- | ||
{ | ||
"apps": { | ||
"http": { | ||
"servers": { | ||
"srv0": { | ||
"listen": [ | ||
":8080" | ||
], | ||
"routes": [ | ||
{ | ||
"handle": [ | ||
{ | ||
"body": "Hello World", | ||
"handler": "static_response", | ||
"status_code": 200 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
}, | ||
"metrics": { | ||
"labels": { | ||
"client_ip": "{http.request.remote}", | ||
"host": "{http.request.host}", | ||
"method": "{http.request.method}", | ||
"proto": "{http.request.proto}" | ||
} | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,11 @@ type Metrics struct { | |
// managed by Caddy. | ||
PerHost bool `json:"per_host,omitempty"` | ||
|
||
// Labels allows users to define custom labels for metrics. | ||
// The value can use placeholders like {http.request.scheme}, {http.request.proto}, {http.request.remote}, etc. | ||
// These labels will be added to all HTTP metrics. | ||
Labels map[string]string `json:"labels,omitempty"` | ||
|
||
init sync.Once | ||
httpMetrics *httpMetrics `json:"-"` | ||
} | ||
|
@@ -44,6 +49,12 @@ func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) { | |
if metrics.PerHost { | ||
basicLabels = append(basicLabels, "host") | ||
} | ||
if metrics.Labels != nil { | ||
for key := range metrics.Labels { | ||
basicLabels = append(basicLabels, key) | ||
} | ||
} | ||
|
||
metrics.httpMetrics.requestInFlight = promauto.With(registry).NewGaugeVec(prometheus.GaugeOpts{ | ||
Namespace: ns, | ||
Subsystem: sub, | ||
|
@@ -71,6 +82,12 @@ func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) { | |
if metrics.PerHost { | ||
httpLabels = append(httpLabels, "host") | ||
} | ||
if metrics.Labels != nil { | ||
for key := range metrics.Labels { | ||
httpLabels = append(httpLabels, key) | ||
} | ||
} | ||
|
||
metrics.httpMetrics.requestDuration = promauto.With(registry).NewHistogramVec(prometheus.HistogramOpts{ | ||
Namespace: ns, | ||
Subsystem: sub, | ||
|
@@ -111,6 +128,36 @@ func serverNameFromContext(ctx context.Context) string { | |
return srv.name | ||
} | ||
|
||
// processCustomLabels processes custom labels by replacing placeholders with actual values. | ||
func (h *metricsInstrumentedHandler) processCustomLabels(r *http.Request) prometheus.Labels { | ||
labels := make(prometheus.Labels) | ||
|
||
if h.metrics.Labels == nil { | ||
return labels | ||
} | ||
|
||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | ||
if repl == nil { | ||
repl = caddy.NewReplacer() | ||
} | ||
|
||
for key, value := range h.metrics.Labels { | ||
if strings.Contains(value, "{") && strings.Contains(value, "}") { | ||
replaced := repl.ReplaceAll(value, "") | ||
|
||
if replaced == "" || replaced == value { | ||
replaced = "unknown" | ||
} | ||
|
||
labels[key] = replaced | ||
} else { | ||
labels[key] = value | ||
} | ||
} | ||
|
||
return labels | ||
} | ||
|
||
type metricsInstrumentedHandler struct { | ||
handler string | ||
mh MiddlewareHandler | ||
|
@@ -138,6 +185,12 @@ func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Re | |
statusLabels["host"] = strings.ToLower(r.Host) | ||
} | ||
|
||
customLabels := h.processCustomLabels(r) | ||
for key, value := range customLabels { | ||
labels[key] = value | ||
statusLabels[key] = value | ||
} | ||
|
||
Comment on lines
+188
to
+193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I'm afraid of, happened. I've just tested and all the replaced labels have the value "unknown". Applying replacements here is too early because many of the values in the replacer aren't filled in yet. Can this be moved lower or refactored so it picks up the labeling? I imagine it needs something similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you tell me the labels key that is being recorded as unknown at that point? i thought the only value net yet determined at that point was There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
inFlight := h.metrics.httpMetrics.requestInFlight.With(labels) | ||
inFlight.Inc() | ||
defer inFlight.Dec() | ||
|