Skip to content

Commit c55a925

Browse files
authored
Allow custom labels to be added to net/http metrics (#306)
* Allow custom labels to be added to net/http metrics * CHANGELOG entry * Add license to labeler.go * Address PR Feedback: * Add mutex around access to labels * Add Labeler use to handler example test * Add getter to Labeler
1 parent 5a0832b commit c55a925

File tree

5 files changed

+79
-1
lines changed

5 files changed

+79
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1212

1313
- Benchmark tests for the gRPC instrumentation. (#296)
1414
- Integration testing for the gRPC instrumentation. (#297)
15+
- Allow custom labels to be added to net/http metrics. (#306)
1516

1617
### Changed
1718

instrumentation/net/http/handler.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
165165
},
166166
})
167167

168+
labeler := &Labeler{}
169+
ctx = injectLabeler(ctx, labeler)
170+
168171
h.handler.ServeHTTP(w, r.WithContext(ctx))
169172

170173
setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
171174

172175
// Add request metrics
173176

174-
labels := semconv.HTTPServerMetricAttributesFromHTTPRequest(h.operation, r)
177+
labels := append(labeler.Get(), semconv.HTTPServerMetricAttributesFromHTTPRequest(h.operation, r)...)
175178

176179
h.counters[RequestContentLength].Add(ctx, bw.read, labels...)
177180
h.counters[ResponseContentLength].Add(ctx, rww.written, labels...)

instrumentation/net/http/handler_example_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ func ExampleNewHandler() {
6060
otelhttp.WithRouteTag("/hello/:name", http.HandlerFunc(
6161
func(w http.ResponseWriter, r *http.Request) {
6262
ctx := r.Context()
63+
labeler, _ := otelhttp.LabelerFromContext(ctx)
64+
6365
var name string
6466
// Wrap another function in its own span
6567
if err := func(ctx context.Context) error {
@@ -72,19 +74,22 @@ func ExampleNewHandler() {
7274
}(ctx); err != nil {
7375
log.Println("error figuring out name: ", err)
7476
http.Error(w, err.Error(), http.StatusInternalServerError)
77+
labeler.Add(label.Bool("error", true))
7578
return
7679
}
7780

7881
d, err := ioutil.ReadAll(r.Body)
7982
if err != nil {
8083
log.Println("error reading body: ", err)
8184
w.WriteHeader(http.StatusBadRequest)
85+
labeler.Add(label.Bool("error", true))
8286
return
8387
}
8488

8589
n, err := io.WriteString(w, "Hello, "+name+"!\nYou sent me this:\n"+string(d))
8690
if err != nil {
8791
log.Printf("error writing reply after %d bytes: %s", n, err)
92+
labeler.Add(label.Bool("error", true))
8893
}
8994
}),
9095
),

instrumentation/net/http/handler_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ func TestHandlerBasics(t *testing.T) {
5050

5151
h := NewHandler(
5252
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
53+
l, _ := LabelerFromContext(r.Context())
54+
l.Add(label.String("test", "label"))
55+
5356
if _, err := io.WriteString(w, "hello world"); err != nil {
5457
t.Fatal(err)
5558
}
@@ -73,6 +76,7 @@ func TestHandlerBasics(t *testing.T) {
7376
semconv.HTTPSchemeHTTP,
7477
semconv.HTTPHostKey.String(r.Host),
7578
semconv.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)),
79+
label.String("test", "label"),
7680
}
7781

7882
assertMetricLabels(t, labelsToVerify, meterimpl.MeasurementBatches)

instrumentation/net/http/labeler.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright The OpenTelemetry 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 http
16+
17+
import (
18+
"context"
19+
"sync"
20+
21+
"go.opentelemetry.io/otel/label"
22+
)
23+
24+
// Labeler is used to allow instrumented HTTP handlers to add custom labels to
25+
// the metrics recorded by the net/http instrumentation.
26+
type Labeler struct {
27+
mu sync.Mutex
28+
labels []label.KeyValue
29+
}
30+
31+
// Add labels to a Labeler.
32+
func (l *Labeler) Add(ls ...label.KeyValue) {
33+
l.mu.Lock()
34+
defer l.mu.Unlock()
35+
l.labels = append(l.labels, ls...)
36+
}
37+
38+
// Labels returns a copy of the labels added to the Labeler.
39+
func (l *Labeler) Get() []label.KeyValue {
40+
l.mu.Lock()
41+
defer l.mu.Unlock()
42+
ret := make([]label.KeyValue, len(l.labels))
43+
copy(ret, l.labels)
44+
return ret
45+
}
46+
47+
type labelerContextKeyType int
48+
49+
const lablelerContextKey labelerContextKeyType = 0
50+
51+
func injectLabeler(ctx context.Context, l *Labeler) context.Context {
52+
return context.WithValue(ctx, lablelerContextKey, l)
53+
}
54+
55+
// LabelerFromContext retrieves a Labeler instance from the provided context if
56+
// one is available. If no Labeler was found in the provided context a new, empty
57+
// Labeler is returned and the second return value is false. In this case it is
58+
// safe to use the Labeler but any labels added to it will not be used.
59+
func LabelerFromContext(ctx context.Context) (*Labeler, bool) {
60+
l, ok := ctx.Value(lablelerContextKey).(*Labeler)
61+
if !ok {
62+
l = &Labeler{}
63+
}
64+
return l, ok
65+
}

0 commit comments

Comments
 (0)