Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1f27a0c
fix(otelhttp): record client metrics on round trip errors
alimikegami Apr 3, 2025
e604a87
refactor: fix code duplication
alimikegami Apr 8, 2025
5fae8b3
refactor: remove code duplication by using defer
alimikegami Apr 9, 2025
c0daa42
test(transport): add metrics checking on request error
alimikegami Apr 9, 2025
8bf794a
refactor(otelhttp): close response body
alimikegami May 19, 2025
fcc71ee
test(otelhttp): add logs to unit test for debugging
alimikegami May 20, 2025
7cda07a
Merge branch 'open-telemetry:main' into fix/6940-otelhttp-client-metr…
alimikegami May 21, 2025
a2476c8
test(otelhttp): update request metrics name
alimikegami May 21, 2025
a12084e
docs: add changelog for request size and duration recording on transp…
alimikegami May 21, 2025
5c0dd92
docs: update CHANGELOG.md
alimikegami May 22, 2025
2d10c70
refactor(otelhttp): remove record metrics function
alimikegami May 22, 2025
9a9d726
Merge branch 'main' into fix/6940-otelhttp-client-metrics-error
alimikegami May 23, 2025
2c42ebe
docs: move changelog entries to unreleased section
alimikegami May 23, 2025
08233ad
docs: fix unreleased section blank lines
alimikegami May 24, 2025
645ebc0
Merge branch 'main' into fix/6940-otelhttp-client-metrics-error
pellared Jun 16, 2025
09a51c2
Update CHANGELOG.md
pellared Jun 16, 2025
18a32cb
refactor(otelhttp): record status code with deferred function
alimikegami Jun 17, 2025
99c76ec
refactor(test): simplify metrics recording test
alimikegami Jun 17, 2025
7bc8b76
Merge branch 'fix/6940-otelhttp-client-metrics-error' of ssh://github…
alimikegami Jun 17, 2025
fc7196a
Merge branch 'main' into fix/6940-otelhttp-client-metrics-error
pellared Jun 17, 2025
435ce71
fix(otelhttp): fix status code recording process
alimikegami Jun 17, 2025
0d7034d
Merge branch 'main' into fix/6940-otelhttp-client-metrics-error
pellared Jun 17, 2025
47d12a1
Update instrumentation/net/http/otelhttp/transport.go
pellared Jun 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Fix data race when writing log entries with `context.Context` fields in `go.opentelemetry.io/contrib/bridges/otelzap`. (#7368)
- Fix nil pointer dereference when `ClientTracer` did not have a span in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#7464)
- Record all non-failure metrics on transport round trip errors in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#7146)

<!-- Released section -->
<!-- Don't change this section unless doing release -->
Expand Down
58 changes: 36 additions & 22 deletions instrumentation/net/http/otelhttp/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,41 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))

res, err := t.rt.RoundTrip(r)

// Defer metrics recording function to record the metrics on error or no error.
defer func() {
metricAttributes := semconv.MetricAttributes{
Req: r,
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
}

if err == nil {
metricAttributes.StatusCode = res.StatusCode
}

metricOpts := t.semconv.MetricOptions(metricAttributes)

metricData := semconv.MetricData{
RequestSize: bw.BytesRead(),
}

if err == nil {
// For handling response bytes we leverage a callback when the client reads the http response
readRecordFunc := func(n int64) {
t.semconv.RecordResponseSize(ctx, n, metricOpts)
}

res.Body = newWrappedBody(span, readRecordFunc, res.Body)
}

// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)

metricData.ElapsedTime = elapsedTime

t.semconv.RecordMetrics(ctx, metricData, metricOpts)
}()

if err != nil {
// set error type attribute if the error is part of the predefined
// error types.
Expand All @@ -141,35 +176,14 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {

span.SetStatus(codes.Error, err.Error())
span.End()
return res, err
}

// metrics
metricOpts := t.semconv.MetricOptions(semconv.MetricAttributes{
Req: r,
StatusCode: res.StatusCode,
AdditionalAttributes: append(labeler.Get(), t.metricAttributesFromRequest(r)...),
})

// For handling response bytes we leverage a callback when the client reads the http response
readRecordFunc := func(n int64) {
t.semconv.RecordResponseSize(ctx, n, metricOpts)
return res, err
}

// traces
span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...)
span.SetStatus(t.semconv.Status(res.StatusCode))

res.Body = newWrappedBody(span, readRecordFunc, res.Body)

// Use floating point division here for higher precision (instead of Millisecond method).
elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)

t.semconv.RecordMetrics(ctx, semconv.MetricData{
RequestSize: bw.BytesRead(),
ElapsedTime: elapsedTime,
}, metricOpts)

return res, nil
}

Expand Down
37 changes: 37 additions & 0 deletions instrumentation/net/http/otelhttp/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,43 @@ func TestCustomAttributesHandling(t *testing.T) {
}
}

func TestMetricsExistenceOnRequestError(t *testing.T) {
var rm metricdata.ResourceMetrics

ctx := context.Background()
reader := sdkmetric.NewManualReader()
provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader))
defer func() {
err := provider.Shutdown(ctx)
assert.NoError(t, err)
}()

transport := NewTransport(http.DefaultTransport, WithMeterProvider(provider))
client := http.Client{Transport: transport}

// simulate an error by closing the server
// before the request is made
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
ts.Close()

r, err := http.NewRequest(http.MethodGet, ts.URL, nil)
require.NoError(t, err)

resp, err := client.Do(r)
if err == nil {
e := resp.Body.Close()
assert.NoError(t, e)
}

require.Error(t, err)

err = reader.Collect(ctx, &rm)
require.NoError(t, err)

// make sure client request size and duration metrics is recorded
assert.Len(t, rm.ScopeMetrics[0].Metrics, 2, "should record client request size and duration metrics")
}

func TestDefaultAttributesHandling(t *testing.T) {
var rm metricdata.ResourceMetrics
const (
Expand Down