Skip to content

Commit

Permalink
Add support for the WithMetricAttributesFn option for otelhttp midd…
Browse files Browse the repository at this point in the history
…lewares (#6542)

Co-authored-by: Damien Mathieu <[email protected]>
  • Loading branch information
TanishqPorwar and dmathieu authored Jan 6, 2025
1 parent 96acbc0 commit 6ac9f50
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Added SNS instrumentation in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#6388)
- Use a `sync.Pool` for metric options in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6394)
- Added support for configuring `Certificate` field when configuring OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6376)
- Added support for the `WithMetricAttributesFn` option to middlewares in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6542)

### Changed

Expand Down
45 changes: 29 additions & 16 deletions instrumentation/net/http/otelhttp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
Expand All @@ -21,15 +22,16 @@ type middleware struct {
operation string
server string

tracer trace.Tracer
propagators propagation.TextMapPropagator
spanStartOptions []trace.SpanStartOption
readEvent bool
writeEvent bool
filters []Filter
spanNameFormatter func(string, *http.Request) string
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
tracer trace.Tracer
propagators propagation.TextMapPropagator
spanStartOptions []trace.SpanStartOption
readEvent bool
writeEvent bool
filters []Filter
spanNameFormatter func(string, *http.Request) string
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
metricAttributesFn func(*http.Request) []attribute.KeyValue

semconv semconv.HTTPServer
}
Expand Down Expand Up @@ -79,6 +81,7 @@ func (h *middleware) configure(c *config) {
h.publicEndpointFn = c.PublicEndpointFn
h.server = c.ServerName
h.semconv = semconv.NewHTTPServer(c.Meter)
h.metricAttributesFn = c.MetricAttributesFn
}

// serveHTTP sets up tracing and calls the given next http.Handler with the span
Expand Down Expand Up @@ -189,21 +192,31 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)

metricAttributes := semconv.MetricAttributes{
Req: r,
StatusCode: statusCode,
AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
}

h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{
ServerName: h.server,
ResponseSize: bytesWritten,
MetricAttributes: semconv.MetricAttributes{
Req: r,
StatusCode: statusCode,
AdditionalAttributes: labeler.Get(),
},
ServerName: h.server,
ResponseSize: bytesWritten,
MetricAttributes: metricAttributes,
MetricData: semconv.MetricData{
RequestSize: bw.BytesRead(),
ElapsedTime: elapsedTime,
},
})
}

func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue {
var attributeForRequest []attribute.KeyValue
if h.metricAttributesFn != nil {
attributeForRequest = h.metricAttributesFn(r)
}
return attributeForRequest
}

// WithRouteTag annotates spans and metrics with the provided route name
// with HTTP route attribute.
func WithRouteTag(route string, h http.Handler) http.Handler {
Expand Down
72 changes: 72 additions & 0 deletions instrumentation/net/http/otelhttp/test/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,78 @@ func TestWithRouteTag(t *testing.T) {
}
}

func TestHandlerWithMetricAttributesFn(t *testing.T) {
const (
serverRequestSize = "http.server.request.size"
serverResponseSize = "http.server.response.size"
serverDuration = "http.server.duration"
)
testCases := []struct {
name string
fn func(r *http.Request) []attribute.KeyValue
expectedAdditionalAttribute []attribute.KeyValue
}{
{
name: "With a nil function",
fn: nil,
expectedAdditionalAttribute: []attribute.KeyValue{},
},
{
name: "With a function that returns an additional attribute",
fn: func(r *http.Request) []attribute.KeyValue {
return []attribute.KeyValue{
attribute.String("fooKey", "fooValue"),
attribute.String("barKey", "barValue"),
}
},
expectedAdditionalAttribute: []attribute.KeyValue{
attribute.String("fooKey", "fooValue"),
attribute.String("barKey", "barValue"),
},
},
}

for _, tc := range testCases {
reader := sdkmetric.NewManualReader()
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))

h := otelhttp.NewHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}), "test_handler",
otelhttp.WithMeterProvider(meterProvider),
otelhttp.WithMetricAttributesFn(tc.fn),
)

r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
require.NoError(t, err)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, r)

rm := metricdata.ResourceMetrics{}
err = reader.Collect(context.Background(), &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1)
assert.Len(t, rm.ScopeMetrics[0].Metrics, 3)

// Verify that the additional attribute is present in the metrics.
for _, m := range rm.ScopeMetrics[0].Metrics {
switch m.Name {
case serverRequestSize, serverResponseSize:
d, ok := m.Data.(metricdata.Sum[int64])
assert.True(t, ok)
assert.Len(t, d.DataPoints, 1)
containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].expectedAdditionalAttribute)
case serverDuration:
d, ok := m.Data.(metricdata.Histogram[float64])
assert.True(t, ok)
assert.Len(t, d.DataPoints, 1)
containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].expectedAdditionalAttribute)
}
}
}
}

func BenchmarkHandlerServeHTTP(b *testing.B) {
tp := sdktrace.NewTracerProvider()
mp := sdkmetric.NewMeterProvider()
Expand Down

0 comments on commit 6ac9f50

Please sign in to comment.