Skip to content
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

otelhttp: Allow setting start time using context #6137

Merged
merged 8 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Transform raw (`slog.KindAny`) attribute values to matching `log.Value` types.
For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254)
- Add the `WithSource` option to the `go.opentelemetry.io/contrib/bridges/otelslog` log bridge to set the `code.*` attributes in the log record that includes the source location where the record was emitted. (#6253)
- Add `ContextWithStartTime` and `StartTimeFromContext` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which allows setting the start time using go context. (#6137)

### Fixed

Expand Down
5 changes: 5 additions & 0 deletions instrumentation/net/http/otelhttp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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()

Expand Down
29 changes: 29 additions & 0 deletions instrumentation/net/http/otelhttp/start_time_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 {
dmathieu marked this conversation as resolved.
Show resolved Hide resolved
t, _ := ctx.Value(startTimeContextKey).(time.Time)
return t
}
23 changes: 23 additions & 0 deletions instrumentation/net/http/otelhttp/start_time_context_test.go
Original file line number Diff line number Diff line change
@@ -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.True(t, startTime.Equal(now))
}
8 changes: 8 additions & 0 deletions instrumentation/net/http/otelhttp/test/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -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.GreaterOrEqual(t, sm.Metrics[2].Data.(metricdata.Histogram[float64]).DataPoints[0].Sum, float64(10*time.Minute/time.Millisecond))
}

func TestHandlerBasics(t *testing.T) {
Expand Down Expand Up @@ -102,6 +106,9 @@ func TestHandlerBasics(t *testing.T) {
if err != nil {
t.Fatal(err)
}
// set a custom start time 10 minutes in the past.
startTime := time.Now().Add(-10 * time.Minute)
r = r.WithContext(otelhttp.ContextWithStartTime(r.Context(), startTime))
h.ServeHTTP(rr, r)

rm := metricdata.ResourceMetrics{}
Expand Down Expand Up @@ -138,6 +145,7 @@ func TestHandlerBasics(t *testing.T) {
if got, expected := string(d), "hello world"; got != expected {
t.Fatalf("got %q, expected %q", got, expected)
}
assert.Equal(t, startTime, spans[0].StartTime())
}

func TestHandlerEmittedAttributes(t *testing.T) {
Expand Down
Loading