diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d863737be0..3f813a61e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - The `Severitier` and `SeverityVar` types are added to `go.opentelemetry.io/contrib/processors/minsev` allowing dynamic configuration of the severity used by the `LogProcessor`. (#6116) +- Add `ContextWithStartTime` and `StartTimeFromContext` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which allows setting the start time using go context. (#6137) ### Changed diff --git a/instrumentation/net/http/otelhttp/handler.go b/instrumentation/net/http/otelhttp/handler.go index 33580a35b77..2f2fbf59fee 100644 --- a/instrumentation/net/http/otelhttp/handler.go +++ b/instrumentation/net/http/otelhttp/handler.go @@ -123,6 +123,11 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http } } + if startTime := StartTimeFromContext(ctx); !startTime.IsZero() { + opts = append(opts, trace.WithTimestamp(startTime)) + requestStartTime = startTime + } + ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...) defer span.End() diff --git a/instrumentation/net/http/otelhttp/start_time_context.go b/instrumentation/net/http/otelhttp/start_time_context.go new file mode 100644 index 00000000000..19631bff4c2 --- /dev/null +++ b/instrumentation/net/http/otelhttp/start_time_context.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + +import ( + "context" + "time" +) + +type startTimeContextKeyType int + +const startTimeContextKey startTimeContextKeyType = 0 + +// ContextWithStartTime returns a new context with the provided start time. The +// start time will be used for metrics and traces emitted by the +// instrumentation. Only one labeller can be injected into the context. +// Injecting it multiple times will override the previous calls. +func ContextWithStartTime(parent context.Context, start time.Time) context.Context { + return context.WithValue(parent, startTimeContextKey, start) +} + +// StartTimeFromContext retrieves a time.Time from the provided context if one +// is available. If no start time was found in the provided context, a new, +// zero start time is returned and the second return value is false. +func StartTimeFromContext(ctx context.Context) time.Time { + t, _ := ctx.Value(startTimeContextKey).(time.Time) + return t +} + +var zeroTime time.Time diff --git a/instrumentation/net/http/otelhttp/start_time_context_test.go b/instrumentation/net/http/otelhttp/start_time_context_test.go new file mode 100644 index 00000000000..1565fc9746a --- /dev/null +++ b/instrumentation/net/http/otelhttp/start_time_context_test.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelhttp + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestStartTimeFromContext(t *testing.T) { + ctx := context.Background() + startTime := StartTimeFromContext(ctx) + assert.True(t, startTime.IsZero()) + now := time.Now() + ctx = ContextWithStartTime(ctx, now) + startTime = StartTimeFromContext(ctx) + assert.False(t, startTime.IsZero()) + assert.True(t, startTime.Equal(now)) +} diff --git a/instrumentation/net/http/otelhttp/test/handler_test.go b/instrumentation/net/http/otelhttp/test/handler_test.go index 57e5544783d..002b86f0b77 100644 --- a/instrumentation/net/http/otelhttp/test/handler_test.go +++ b/instrumentation/net/http/otelhttp/test/handler_test.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -72,6 +73,9 @@ func assertScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribut }, } metricdatatest.AssertEqual(t, want, sm.Metrics[2], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + + // verify that the custom start time, which is 10 minutes in the past, is respected. + assert.Greater(t, sm.Metrics[2].Data.(metricdata.Histogram[float64]).DataPoints[0].Sum, float64(10*time.Minute/time.Millisecond)) } func TestHandlerBasics(t *testing.T) { @@ -101,6 +105,8 @@ func TestHandlerBasics(t *testing.T) { if err != nil { t.Fatal(err) } + // set a custom start time 10 minutes in the past. + r = r.WithContext(otelhttp.ContextWithStartTime(r.Context(), time.Now().Add(-10*time.Minute))) h.ServeHTTP(rr, r) rm := metricdata.ResourceMetrics{}