vendor: go.opentelemetry.io/otel v1.42.0, otel/contrib v1.67.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-04-04 01:41:01 +02:00
parent b180175396
commit 63b4d001aa
105 changed files with 3838 additions and 5972 deletions
@@ -31,14 +31,16 @@ type Filter func(*stats.RPCTagInfo) bool
// config is a group of options for this instrumentation.
type config struct {
Filter Filter
InterceptorFilter InterceptorFilter
Propagators propagation.TextMapPropagator
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
SpanStartOptions []trace.SpanStartOption
SpanAttributes []attribute.KeyValue
MetricAttributes []attribute.KeyValue
Filter Filter
InterceptorFilter InterceptorFilter
Propagators propagation.TextMapPropagator
TracerProvider trace.TracerProvider
MeterProvider metric.MeterProvider
SpanKind trace.SpanKind
SpanStartOptions []trace.SpanStartOption
SpanAttributes []attribute.KeyValue
MetricAttributes []attribute.KeyValue
MetricAttributesFn func(ctx context.Context) []attribute.KeyValue
PublicEndpoint bool
PublicEndpointFn func(ctx context.Context, info *stats.RPCTagInfo) bool
@@ -52,6 +54,12 @@ type Option interface {
apply(*config)
}
type optionFunc func(*config)
func (f optionFunc) apply(c *config) {
f(c)
}
// newConfig returns a config configured with all the passed Options.
func newConfig(opts []Option) *config {
c := &config{
@@ -65,27 +73,13 @@ func newConfig(opts []Option) *config {
return c
}
type publicEndpointOption struct{ p bool }
func (o publicEndpointOption) apply(c *config) {
c.PublicEndpoint = o.p
}
// WithPublicEndpoint configures the Handler to link the span with an incoming
// span context. If this option is not provided, then the association is a child
// association instead of a link.
func WithPublicEndpoint() Option {
return publicEndpointOption{p: true}
}
type publicEndpointFnOption struct {
fn func(context.Context, *stats.RPCTagInfo) bool
}
func (o publicEndpointFnOption) apply(c *config) {
if o.fn != nil {
c.PublicEndpointFn = o.fn
}
return optionFunc(func(c *config) {
c.PublicEndpoint = true
})
}
// WithPublicEndpointFn runs with every request, and allows conditionally
@@ -94,81 +88,59 @@ func (o publicEndpointFnOption) apply(c *config) {
// child association instead of a link.
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
func WithPublicEndpointFn(fn func(context.Context, *stats.RPCTagInfo) bool) Option {
return publicEndpointFnOption{fn: fn}
}
type propagatorsOption struct{ p propagation.TextMapPropagator }
func (o propagatorsOption) apply(c *config) {
if o.p != nil {
c.Propagators = o.p
}
return optionFunc(func(c *config) {
c.PublicEndpointFn = fn
})
}
// WithPropagators returns an Option to use the Propagators when extracting
// and injecting trace context from requests.
func WithPropagators(p propagation.TextMapPropagator) Option {
return propagatorsOption{p: p}
}
type tracerProviderOption struct{ tp trace.TracerProvider }
func (o tracerProviderOption) apply(c *config) {
if o.tp != nil {
c.TracerProvider = o.tp
}
return optionFunc(func(c *config) {
if p != nil {
c.Propagators = p
}
})
}
// WithInterceptorFilter returns an Option to use the request filter.
//
// Deprecated: Use stats handlers instead.
func WithInterceptorFilter(f InterceptorFilter) Option {
return interceptorFilterOption{f: f}
}
type interceptorFilterOption struct {
f InterceptorFilter
}
func (o interceptorFilterOption) apply(c *config) {
if o.f != nil {
c.InterceptorFilter = o.f
}
return optionFunc(func(c *config) {
if f != nil {
c.InterceptorFilter = f
}
})
}
// WithFilter returns an Option to use the request filter.
func WithFilter(f Filter) Option {
return filterOption{f: f}
}
type filterOption struct {
f Filter
}
func (o filterOption) apply(c *config) {
if o.f != nil {
c.Filter = o.f
}
return optionFunc(func(c *config) {
if f != nil {
c.Filter = f
}
})
}
// WithTracerProvider returns an Option to use the TracerProvider when
// creating a Tracer.
func WithTracerProvider(tp trace.TracerProvider) Option {
return tracerProviderOption{tp: tp}
}
type meterProviderOption struct{ mp metric.MeterProvider }
func (o meterProviderOption) apply(c *config) {
if o.mp != nil {
c.MeterProvider = o.mp
}
return optionFunc(func(c *config) {
if tp != nil {
c.TracerProvider = tp
}
})
}
// WithMeterProvider returns an Option to use the MeterProvider when
// creating a Meter. If this option is not provide the global MeterProvider will be used.
func WithMeterProvider(mp metric.MeterProvider) Option {
return meterProviderOption{mp: mp}
return optionFunc(func(c *config) {
if mp != nil {
c.MeterProvider = mp
}
})
}
// Event type that can be recorded, see WithMessageEvents.
@@ -180,21 +152,6 @@ const (
SentEvents
)
type messageEventsProviderOption struct {
events []Event
}
func (m messageEventsProviderOption) apply(c *config) {
for _, e := range m.events {
switch e {
case ReceivedEvents:
c.ReceivedEvent = true
case SentEvents:
c.SentEvent = true
}
}
}
// WithMessageEvents configures the Handler to record the specified events
// (span.AddEvent) on spans. By default only summary attributes are added at the
// end of the request.
@@ -203,13 +160,16 @@ func (m messageEventsProviderOption) apply(c *config) {
// - ReceivedEvents: Record the number of bytes read after every gRPC read operation.
// - SentEvents: Record the number of bytes written after every gRPC write operation.
func WithMessageEvents(events ...Event) Option {
return messageEventsProviderOption{events: events}
}
type spanStartOption struct{ opts []trace.SpanStartOption }
func (o spanStartOption) apply(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, o.opts...)
return optionFunc(func(c *config) {
for _, e := range events {
switch e {
case ReceivedEvents:
c.ReceivedEvent = true
case SentEvents:
c.SentEvent = true
}
}
})
}
// WithSpanOptions configures an additional set of
@@ -217,31 +177,49 @@ func (o spanStartOption) apply(c *config) {
//
// Deprecated: It is only used by the deprecated interceptor, and is unused by [NewClientHandler] and [NewServerHandler].
func WithSpanOptions(opts ...trace.SpanStartOption) Option {
return spanStartOption{opts}
return optionFunc(func(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
})
}
type spanAttributesOption struct{ a []attribute.KeyValue }
func (o spanAttributesOption) apply(c *config) {
if o.a != nil {
c.SpanAttributes = o.a
}
// WithSpanKind returns an Option to set the span kind for spans created by
// the handler.
//
// By default, [NewServerHandler] creates spans with
// [trace.SpanKindServer] and [NewClientHandler] creates spans with
// [trace.SpanKindClient].
func WithSpanKind(sk trace.SpanKind) Option {
return optionFunc(func(c *config) {
c.SpanKind = sk
})
}
// WithSpanAttributes returns an Option to add custom attributes to the spans.
func WithSpanAttributes(a ...attribute.KeyValue) Option {
return spanAttributesOption{a: a}
}
type metricAttributesOption struct{ a []attribute.KeyValue }
func (o metricAttributesOption) apply(c *config) {
if o.a != nil {
c.MetricAttributes = o.a
}
return optionFunc(func(c *config) {
if a != nil {
c.SpanAttributes = a
}
})
}
// WithMetricAttributes returns an Option to add custom attributes to the metrics.
func WithMetricAttributes(a ...attribute.KeyValue) Option {
return metricAttributesOption{a: a}
return optionFunc(func(c *config) {
if a != nil {
c.MetricAttributes = append(c.MetricAttributes, a...)
}
})
}
// WithMetricAttributesFn returns an Option to add dynamic custom attributes to the handler's metrics.
// The function is called once per RPC and the returned attributes are applied to all metrics recorded by this handler.
//
// The context parameter is the standard gRPC request context and provides access to request-scoped data.
func WithMetricAttributesFn(fn func(ctx context.Context) []attribute.KeyValue) Option {
return optionFunc(func(c *config) {
if fn != nil {
c.MetricAttributesFn = fn
}
})
}
@@ -11,7 +11,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
grpc_codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -8,7 +8,7 @@ import (
"strings"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
// ParseFullMethod returns a span name following the OpenTelemetry semantic
@@ -23,19 +23,5 @@ func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
return fullMethod, nil
}
name := fullMethod[1:]
pos := strings.LastIndex(name, "/")
if pos < 0 {
// Invalid format, does not follow `/package.service/method`.
return name, nil
}
service, method := name[:pos], name[pos+1:]
var attrs []attribute.KeyValue
if service != "" {
attrs = append(attrs, semconv.RPCService(service))
}
if method != "" {
attrs = append(attrs, semconv.RPCMethod(method))
}
return name, attrs
return name, []attribute.KeyValue{semconv.RPCMethod(name)}
}
@@ -6,9 +6,7 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
import (
"context"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/metadata"
)
@@ -17,9 +15,9 @@ type metadataSupplier struct {
}
// assert that metadataSupplier implements the TextMapCarrier interface.
var _ propagation.TextMapCarrier = &metadataSupplier{}
var _ propagation.TextMapCarrier = metadataSupplier{}
func (s *metadataSupplier) Get(key string) string {
func (s metadataSupplier) Get(key string) string {
values := s.metadata.Get(key)
if len(values) == 0 {
return ""
@@ -27,11 +25,11 @@ func (s *metadataSupplier) Get(key string) string {
return values[0]
}
func (s *metadataSupplier) Set(key, value string) {
func (s metadataSupplier) Set(key, value string) {
s.metadata.Set(key, value)
}
func (s *metadataSupplier) Keys() []string {
func (s metadataSupplier) Keys() []string {
out := make([]string, 0, len(s.metadata))
for key := range s.metadata {
out = append(out, key)
@@ -39,50 +37,24 @@ func (s *metadataSupplier) Keys() []string {
return out
}
// Inject injects correlation context and span context into the gRPC
// metadata object. This function is meant to be used on outgoing
// requests.
//
// Deprecated: Unnecessary public func.
func Inject(ctx context.Context, md *metadata.MD, opts ...Option) {
c := newConfig(opts)
c.Propagators.Inject(ctx, &metadataSupplier{
metadata: *md,
})
}
func inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.MD{}
}
propagators.Inject(ctx, &metadataSupplier{
propagators.Inject(ctx, metadataSupplier{
metadata: md,
})
return metadata.NewOutgoingContext(ctx, md)
}
// Extract returns the correlation context and span context that
// another service encoded in the gRPC metadata object with Inject.
// This function is meant to be used on incoming requests.
//
// Deprecated: Unnecessary public func.
func Extract(ctx context.Context, md *metadata.MD, opts ...Option) (baggage.Baggage, trace.SpanContext) {
c := newConfig(opts)
ctx = c.Propagators.Extract(ctx, &metadataSupplier{
metadata: *md,
})
return baggage.FromContext(ctx), trace.SpanContextFromContext(ctx)
}
func extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.MD{}
}
return propagators.Extract(ctx, &metadataSupplier{
return propagators.Extract(ctx, metadataSupplier{
metadata: md,
})
}
@@ -5,15 +5,15 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g
import (
"context"
"sync/atomic"
"strconv"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/rpcconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/rpcconv"
"go.opentelemetry.io/otel/trace"
grpc_codes "google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
@@ -26,8 +26,6 @@ import (
type gRPCContextKey struct{}
type gRPCContext struct {
inMessages int64
outMessages int64
metricAttrs []attribute.KeyValue
record bool
}
@@ -37,51 +35,37 @@ type serverHandler struct {
tracer trace.Tracer
duration rpcconv.ServerDuration
inSize rpcconv.ServerRequestSize
outSize rpcconv.ServerResponseSize
inMsg rpcconv.ServerRequestsPerRPC
outMsg rpcconv.ServerResponsesPerRPC
duration rpcconv.ServerCallDuration
}
// NewServerHandler creates a stats.Handler for a gRPC server.
func NewServerHandler(opts ...Option) stats.Handler {
c := newConfig(opts)
if c.SpanKind == trace.SpanKindUnspecified {
c.SpanKind = trace.SpanKindServer
}
h := &serverHandler{config: c}
h.tracer = c.TracerProvider.Tracer(
ScopeName,
trace.WithInstrumentationVersion(Version()),
trace.WithInstrumentationVersion(Version),
)
meter := c.MeterProvider.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version()),
metric.WithInstrumentationVersion(Version),
metric.WithSchemaURL(semconv.SchemaURL),
)
var err error
h.duration, err = rpcconv.NewServerDuration(meter)
if err != nil {
otel.Handle(err)
}
h.inSize, err = rpcconv.NewServerRequestSize(meter)
if err != nil {
otel.Handle(err)
}
h.outSize, err = rpcconv.NewServerResponseSize(meter)
if err != nil {
otel.Handle(err)
}
h.inMsg, err = rpcconv.NewServerRequestsPerRPC(meter)
if err != nil {
otel.Handle(err)
}
h.outMsg, err = rpcconv.NewServerResponsesPerRPC(meter)
h.duration, err = rpcconv.NewServerCallDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
if err != nil {
otel.Handle(err)
}
@@ -103,7 +87,7 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
ctx = extract(ctx, h.Propagators)
name, attrs := internal.ParseFullMethod(info.FullMethodName)
attrs = append(attrs, semconv.RPCSystemGRPC)
attrs = append(attrs, semconv.RPCSystemNameGRPC)
record := true
if h.Filter != nil {
@@ -111,9 +95,12 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
}
if record {
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
opts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(append(attrs, h.SpanAttributes...)...),
trace.WithSpanKind(h.SpanKind),
trace.WithAttributes(spanAttributes...),
}
if h.PublicEndpoint || (h.PublicEndpointFn != nil && h.PublicEndpointFn(ctx, info)) {
opts = append(opts, trace.WithNewRoot())
@@ -134,6 +121,11 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
record: record,
}
if h.MetricAttributesFn != nil {
extraAttrs := h.MetricAttributesFn(ctx)
gctx.metricAttrs = append(gctx.metricAttrs, extraAttrs...)
}
return context.WithValue(ctx, gRPCContextKey{}, &gctx)
}
@@ -143,10 +135,6 @@ func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ctx,
rs,
h.duration.Inst(),
h.inSize,
h.outSize,
h.inMsg.Inst(),
h.outMsg.Inst(),
serverStatus,
)
}
@@ -156,51 +144,37 @@ type clientHandler struct {
tracer trace.Tracer
duration rpcconv.ClientDuration
inSize rpcconv.ClientResponseSize
outSize rpcconv.ClientRequestSize
inMsg rpcconv.ClientResponsesPerRPC
outMsg rpcconv.ClientRequestsPerRPC
duration rpcconv.ClientCallDuration
}
// NewClientHandler creates a stats.Handler for a gRPC client.
func NewClientHandler(opts ...Option) stats.Handler {
c := newConfig(opts)
if c.SpanKind == trace.SpanKindUnspecified {
c.SpanKind = trace.SpanKindClient
}
h := &clientHandler{config: c}
h.tracer = c.TracerProvider.Tracer(
ScopeName,
trace.WithInstrumentationVersion(Version()),
trace.WithInstrumentationVersion(Version),
)
meter := c.MeterProvider.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version()),
metric.WithInstrumentationVersion(Version),
metric.WithSchemaURL(semconv.SchemaURL),
)
var err error
h.duration, err = rpcconv.NewClientDuration(meter)
if err != nil {
otel.Handle(err)
}
h.inSize, err = rpcconv.NewClientResponseSize(meter)
if err != nil {
otel.Handle(err)
}
h.outSize, err = rpcconv.NewClientRequestSize(meter)
if err != nil {
otel.Handle(err)
}
h.inMsg, err = rpcconv.NewClientResponsesPerRPC(meter)
if err != nil {
otel.Handle(err)
}
h.outMsg, err = rpcconv.NewClientRequestsPerRPC(meter)
h.duration, err = rpcconv.NewClientCallDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
if err != nil {
otel.Handle(err)
}
@@ -211,7 +185,7 @@ func NewClientHandler(opts ...Option) stats.Handler {
// TagRPC can attach some information to the given context.
func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
name, attrs := internal.ParseFullMethod(info.FullMethodName)
attrs = append(attrs, semconv.RPCSystemGRPC)
attrs = append(attrs, semconv.RPCSystemNameGRPC)
record := true
if h.Filter != nil {
@@ -219,11 +193,14 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
}
if record {
// Make a new slice to avoid aliasing into the same attrs slice used by metrics.
spanAttributes := make([]attribute.KeyValue, 0, len(attrs)+len(h.SpanAttributes))
spanAttributes = append(append(spanAttributes, attrs...), h.SpanAttributes...)
ctx, _ = h.tracer.Start(
ctx,
name,
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(append(attrs, h.SpanAttributes...)...),
trace.WithSpanKind(h.SpanKind),
trace.WithAttributes(spanAttributes...),
)
}
@@ -232,6 +209,11 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
record: record,
}
if h.MetricAttributesFn != nil {
extraAttrs := h.MetricAttributesFn(ctx)
gctx.metricAttrs = append(gctx.metricAttrs, extraAttrs...)
}
return inject(context.WithValue(ctx, gRPCContextKey{}, &gctx), h.Propagators)
}
@@ -241,10 +223,6 @@ func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
ctx,
rs,
h.duration.Inst(),
h.inSize,
h.outSize,
h.inMsg.Inst(),
h.outMsg.Inst(),
func(s *status.Status) (codes.Code, string) {
return codes.Error, s.Message()
},
@@ -261,16 +239,10 @@ func (*clientHandler) HandleConn(context.Context, stats.ConnStats) {
// no-op
}
type int64Hist interface {
Record(context.Context, int64, ...attribute.KeyValue)
}
func (c *config) handleRPC(
func (*config) handleRPC(
ctx context.Context,
rs stats.RPCStats,
duration metric.Float64Histogram,
inSize, outSize int64Hist,
inMsg, outMsg metric.Int64Histogram,
recordStatus func(*status.Status) (codes.Code, string),
) {
gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext)
@@ -279,42 +251,11 @@ func (c *config) handleRPC(
}
span := trace.SpanFromContext(ctx)
var messageId int64
switch rs := rs.(type) {
case *stats.Begin:
case *stats.InPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.inMessages, 1)
inSize.Record(ctx, int64(rs.Length), gctx.metricAttrs...)
}
if c.ReceivedEvent && span.IsRecording() {
span.AddEvent("message",
trace.WithAttributes(
semconv.RPCMessageTypeReceived,
semconv.RPCMessageIDKey.Int64(messageId),
semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength),
semconv.RPCMessageUncompressedSizeKey.Int(rs.Length),
),
)
}
case *stats.OutPayload:
if gctx != nil {
messageId = atomic.AddInt64(&gctx.outMessages, 1)
outSize.Record(ctx, int64(rs.Length), gctx.metricAttrs...)
}
if c.SentEvent && span.IsRecording() {
span.AddEvent("message",
trace.WithAttributes(
semconv.RPCMessageTypeSent,
semconv.RPCMessageIDKey.Int64(messageId),
semconv.RPCMessageCompressedSizeKey.Int(rs.CompressedLength),
semconv.RPCMessageUncompressedSizeKey.Int(rs.Length),
),
)
}
case *stats.OutTrailer:
case *stats.OutHeader:
if span.IsRecording() {
@@ -328,9 +269,9 @@ func (c *config) handleRPC(
var s *status.Status
if rs.Error != nil {
s, _ = status.FromError(rs.Error)
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(s.Code()))
rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(s.Code()))
} else {
rpcStatusAttr = semconv.RPCGRPCStatusCodeKey.Int(int(grpc_codes.OK))
rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(grpc_codes.OK))
}
if span.IsRecording() {
if s != nil {
@@ -343,6 +284,9 @@ func (c *config) handleRPC(
var metricAttrs []attribute.KeyValue
if gctx != nil {
// Don't use gctx.metricAttrSet here, because it requires passing
// multiple RecordOptions, which would call metric.mergeSets and
// allocate a new set for each Record call.
metricAttrs = make([]attribute.KeyValue, 0, len(gctx.metricAttrs)+1)
metricAttrs = append(metricAttrs, gctx.metricAttrs...)
}
@@ -352,14 +296,51 @@ func (c *config) handleRPC(
// Use floating point division here for higher precision (instead of Millisecond method).
// Measure right before calling Record() to capture as much elapsed time as possible.
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond)
elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Second)
duration.Record(ctx, elapsedTime, recordOpts...)
if gctx != nil {
inMsg.Record(ctx, atomic.LoadInt64(&gctx.inMessages), recordOpts...)
outMsg.Record(ctx, atomic.LoadInt64(&gctx.outMessages), recordOpts...)
}
default:
return
}
}
func canonicalString(code grpc_codes.Code) string {
switch code {
case grpc_codes.OK:
return "OK"
case grpc_codes.Canceled:
return "CANCELLED"
case grpc_codes.Unknown:
return "UNKNOWN"
case grpc_codes.InvalidArgument:
return "INVALID_ARGUMENT"
case grpc_codes.DeadlineExceeded:
return "DEADLINE_EXCEEDED"
case grpc_codes.NotFound:
return "NOT_FOUND"
case grpc_codes.AlreadyExists:
return "ALREADY_EXISTS"
case grpc_codes.PermissionDenied:
return "PERMISSION_DENIED"
case grpc_codes.ResourceExhausted:
return "RESOURCE_EXHAUSTED"
case grpc_codes.FailedPrecondition:
return "FAILED_PRECONDITION"
case grpc_codes.Aborted:
return "ABORTED"
case grpc_codes.OutOfRange:
return "OUT_OF_RANGE"
case grpc_codes.Unimplemented:
return "UNIMPLEMENTED"
case grpc_codes.Internal:
return "INTERNAL"
case grpc_codes.Unavailable:
return "UNAVAILABLE"
case grpc_codes.DataLoss:
return "DATA_LOSS"
case grpc_codes.Unauthenticated:
return "UNAUTHENTICATED"
default:
return "CODE(" + strconv.FormatInt(int64(code), 10) + ")"
}
}
@@ -4,7 +4,4 @@
package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
// Version is the current release version of the gRPC instrumentation.
func Version() string {
return "0.63.0"
// This string is updated by the pre_release.sh script during release
}
const Version = "0.67.0"
@@ -165,7 +165,7 @@ func NewClientTrace(ctx context.Context, opts ...ClientTraceOption) *httptrace.C
ct.tr = ct.tracerProvider.Tracer(
ScopeName,
trace.WithInstrumentationVersion(Version()),
trace.WithInstrumentationVersion(Version),
)
return &httptrace.ClientTrace{
@@ -298,7 +298,7 @@ func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) {
}
func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) {
var addrs []string
addrs := make([]string, 0, len(info.Addrs))
for _, netAddr := range info.Addrs {
addrs = append(addrs, netAddr.String())
}
@@ -0,0 +1,291 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/client.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv"
import (
"context"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
)
type HTTPClient struct {
requestBodySize httpconv.ClientRequestBodySize
requestDuration httpconv.ClientRequestDuration
}
func NewHTTPClient(meter metric.Meter) HTTPClient {
client := HTTPClient{}
var err error
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
handleErr(err)
client.requestDuration, err = httpconv.NewClientRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return client
}
func (n HTTPClient) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
/*
below attributes are returned:
- http.request.method
- http.request.method.original
- url.full
- server.address
- server.port
- network.protocol.name
- network.protocol.version
*/
numOfAttributes := 3 // URL, server address, proto, and method.
var urlHost string
if req.URL != nil {
urlHost = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if eligiblePort > 0 {
numOfAttributes++
}
useragent := req.UserAgent()
if useragent != "" {
numOfAttributes++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
numOfAttributes++
}
if protoVersion != "" {
numOfAttributes++
}
method, originalMethod := n.method(req.Method)
if originalMethod != (attribute.KeyValue{}) {
numOfAttributes++
}
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
attrs = append(attrs, method)
if originalMethod != (attribute.KeyValue{}) {
attrs = append(attrs, originalMethod)
}
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, semconv.URLFull(u))
attrs = append(attrs, semconv.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconv.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
/*
below attributes are returned:
- http.response.status_code
- error.type
*/
var count int
if resp.StatusCode > 0 {
count++
}
if isErrorStatusCode(resp.StatusCode) {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
if resp.StatusCode > 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconv.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 2
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconv.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconv.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
return attributes
}
type MetricOpts struct {
measurement metric.MeasurementOption
addOptions metric.AddOption
}
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
return o.measurement
}
func (o MetricOpts) AddOptions() metric.AddOption {
return o.addOptions
}
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
return MetricOpts{
measurement: set,
addOptions: set,
}
}
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
defer func() {
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}()
*recordOpts = append(*recordOpts, opts.MeasurementOption())
n.requestBodySize.Inst().Record(ctx, md.RequestSize, *recordOpts...)
n.requestDuration.Inst().Record(ctx, durationToSeconds(md.RequestDuration), *recordOpts...)
}
// TraceAttributes returns attributes for httptrace.
func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.ServerAddress(host),
}
}
func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconv.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconv.URLScheme("https")
}
return semconv.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}
@@ -1,248 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/env.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv"
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.37.0/httpconv"
)
// OTelSemConvStabilityOptIn is an environment variable.
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
requestBodySizeHistogram httpconv.ServerRequestBodySize
responseBodySizeHistogram httpconv.ServerResponseBodySize
requestDurationHistogram httpconv.ServerRequestDuration
}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
return []attribute.KeyValue{
CurrentHTTPServer{}.NetworkTransportAttr(network),
}
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
return CurrentHTTPServer{}.ResponseTraceAttrs(resp)
}
// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
return CurrentHTTPServer{}.Route(route)
}
// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (s HTTPServer) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
AdditionalAttributes []attribute.KeyValue
}
type MetricData struct {
RequestSize int64
// The request duration, in milliseconds
ElapsedTime float64
}
var (
metricAddOptionPool = &sync.Pool{
New: func() any {
return &[]metric.AddOption{}
},
}
metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
)
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
s.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
s.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
// hasOptIn returns true if the comma-separated version string contains the
// exact optIn value.
func hasOptIn(version, optIn string) bool {
for _, v := range strings.Split(version, ",") {
if strings.TrimSpace(v) == optIn {
return true
}
}
return false
}
func NewHTTPServer(meter metric.Meter) HTTPServer {
server := HTTPServer{}
var err error
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
handleErr(err)
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
handleErr(err)
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
handleErr(err)
return server
}
type HTTPClient struct {
requestBodySize httpconv.ClientRequestBodySize
requestDuration httpconv.ClientRequestDuration
}
func NewHTTPClient(meter metric.Meter) HTTPClient {
client := HTTPClient{}
var err error
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
handleErr(err)
client.requestDuration, err = httpconv.NewClientRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return client
}
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
return CurrentHTTPClient{}.RequestTraceAttrs(req)
}
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
return CurrentHTTPClient{}.ResponseTraceAttrs(resp)
}
func (c HTTPClient) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
return CurrentHTTPClient{}.ErrorType(err)
}
type MetricOpts struct {
measurement metric.MeasurementOption
addOptions metric.AddOption
}
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
return o.measurement
}
func (o MetricOpts) AddOptions() metric.AddOption {
return o.addOptions
}
func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
opts := map[string]MetricOpts{}
attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
opts["new"] = MetricOpts{
measurement: set,
addOptions: set,
}
return opts
}
func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
}
func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return CurrentHTTPClient{}.TraceAttributes(host)
}
@@ -6,10 +6,10 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/
// Generate semconv package:
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=bench_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=common_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=env.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=env_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=httpconv.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=httpconv_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=httpconvtest_test.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util.go
//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util_test.go
@@ -1,517 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/httpconv.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv"
import (
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
type CurrentHTTPServer struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
peer, peerPort := SplitHostPort(req.RemoteAddr)
if peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}
useragent := req.UserAgent()
if useragent != "" {
count++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
route := httpRoute(req.Pattern)
if route != "" {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconvNew.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconvNew.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)
}
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconvNew.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
switch network {
case "tcp", "tcp4", "tcp6":
return semconvNew.NetworkTransportTCP
case "udp", "udp4", "udp6":
return semconvNew.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
return semconvNew.NetworkTransportUnix
default:
return semconvNew.NetworkTransportPipe
}
}
func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}
func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
if https {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
// response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will
// be omitted.
func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int
if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}
attributes := make([]attribute.KeyValue, 0, count)
if resp.ReadBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n CurrentHTTPServer) Route(route string) attribute.KeyValue {
return semconvNew.HTTPRoute(route)
}
func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconvNew.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconvNew.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
type CurrentHTTPClient struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
/*
below attributes are returned:
- http.request.method
- http.request.method.original
- url.full
- server.address
- server.port
- network.protocol.name
- network.protocol.version
*/
numOfAttributes := 3 // URL, server address, proto, and method.
var urlHost string
if req.URL != nil {
urlHost = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if eligiblePort > 0 {
numOfAttributes++
}
useragent := req.UserAgent()
if useragent != "" {
numOfAttributes++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
numOfAttributes++
}
if protoVersion != "" {
numOfAttributes++
}
method, originalMethod := n.method(req.Method)
if originalMethod != (attribute.KeyValue{}) {
numOfAttributes++
}
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
attrs = append(attrs, method)
if originalMethod != (attribute.KeyValue{}) {
attrs = append(attrs, originalMethod)
}
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, semconvNew.URLFull(u))
attrs = append(attrs, semconvNew.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
/*
below attributes are returned:
- http.response.status_code
- error.type
*/
var count int
if resp.StatusCode > 0 {
count++
}
if isErrorStatusCode(resp.StatusCode) {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
if resp.StatusCode > 0 {
attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
t := reflect.TypeOf(err)
var value string
if t.PkgPath() == "" && t.Name() == "" {
// Likely a builtin type.
value = t.String()
} else {
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
if value == "" {
return semconvNew.ErrorTypeOther
}
return semconvNew.ErrorTypeKey.String(value)
}
func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}
func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 2
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconvNew.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconvNew.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
// TraceAttributes returns attributes for httptrace.
func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconvNew.ServerAddress(host),
}
}
func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconvNew.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}
@@ -0,0 +1,396 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/server.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv"
import (
"context"
"fmt"
"net/http"
"slices"
"strings"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
requestBodySizeHistogram httpconv.ServerRequestBodySize
responseBodySizeHistogram httpconv.ServerResponseBodySize
requestDurationHistogram httpconv.ServerRequestDuration
}
func NewHTTPServer(meter metric.Meter) HTTPServer {
server := HTTPServer{}
var err error
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
handleErr(err)
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
handleErr(err)
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
handleErr(err)
return server
}
// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (n HTTPServer) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
peer, peerPort := SplitHostPort(req.RemoteAddr)
if peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}
useragent := req.UserAgent()
if useragent != "" {
count++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
route := httpRoute(req.Pattern)
if route != "" {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconv.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconv.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)
}
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconv.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconv.NetworkPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, semconv.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconv.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconv.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
attr := semconv.NetworkTransportPipe
switch network {
case "tcp", "tcp4", "tcp6":
attr = semconv.NetworkTransportTCP
case "udp", "udp4", "udp6":
attr = semconv.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
attr = semconv.NetworkTransportUnix
}
return []attribute.KeyValue{attr}
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
Route string
AdditionalAttributes []attribute.KeyValue
}
type MetricData struct {
RequestSize int64
RequestDuration time.Duration
}
var (
metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
)
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
n.requestDurationHistogram.Inst().Record(ctx, durationToSeconds(md.RequestDuration), o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
if https {
return semconv.URLScheme("https")
}
return semconv.URLScheme("http")
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
// response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will
// be omitted.
func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int
if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}
attributes := make([]attribute.KeyValue, 0, count)
if resp.ReadBytes > 0 {
attributes = append(attributes,
semconv.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconv.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconv.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n HTTPServer) Route(route string) attribute.KeyValue {
return semconv.HTTPRoute(route)
}
func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
if route != "" {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconv.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconv.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
if route != "" {
attributes = append(attributes, semconv.HTTPRoute(route))
}
return attributes
}
@@ -11,10 +11,11 @@ import (
"net/http"
"strconv"
"strings"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0"
semconvNew "go.opentelemetry.io/otel/semconv/v1.40.0"
)
// SplitHostPort splits a network address hostport of the form "host",
@@ -125,3 +126,8 @@ func standardizeHTTPMethod(method string) string {
}
return method
}
func durationToSeconds(d time.Duration) float64 {
// Use floating point division here for higher precision (instead of Seconds method).
return float64(d) / float64(time.Second)
}
@@ -4,7 +4,4 @@
package otelhttptrace // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
// Version is the current release version of the httptrace instrumentation.
func Version() string {
return "0.63.0"
// This string is updated by the pre_release.sh script during release
}
const Version = "0.67.0"
@@ -1,50 +0,0 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
import (
"context"
"io"
"net/http"
"net/url"
"strings"
)
// DefaultClient is the default Client and is used by Get, Head, Post and PostForm.
// Please be careful of initialization order - for example, if you change
// the global propagator, the DefaultClient might still be using the old one.
var DefaultClient = &http.Client{Transport: NewTransport(http.DefaultTransport)}
// Get is a convenient replacement for http.Get that adds a span around the request.
func Get(ctx context.Context, targetURL string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, http.NoBody)
if err != nil {
return nil, err
}
return DefaultClient.Do(req)
}
// Head is a convenient replacement for http.Head that adds a span around the request.
func Head(ctx context.Context, targetURL string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodHead, targetURL, http.NoBody)
if err != nil {
return nil, err
}
return DefaultClient.Do(req)
}
// Post is a convenient replacement for http.Post that adds a span around the request.
func Post(ctx context.Context, targetURL, contentType string, body io.Reader) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
return DefaultClient.Do(req)
}
// PostForm is a convenient replacement for http.PostForm that adds a span around the request.
func PostForm(ctx context.Context, targetURL string, data url.Values) (resp *http.Response, err error) {
return Post(ctx, targetURL, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
@@ -23,5 +23,5 @@ const (
type Filter func(*http.Request) bool
func newTracer(tp trace.TracerProvider) trace.Tracer {
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version()))
return tp.Tracer(ScopeName, trace.WithInstrumentationVersion(Version))
}
@@ -26,7 +26,6 @@ type config struct {
Meter metric.Meter
Propagators propagation.TextMapPropagator
SpanStartOptions []trace.SpanStartOption
PublicEndpoint bool
PublicEndpointFn func(*http.Request) bool
ReadEvent bool
WriteEvent bool
@@ -67,7 +66,7 @@ func newConfig(opts ...Option) *config {
c.Meter = c.MeterProvider.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version()),
metric.WithInstrumentationVersion(Version),
)
return c
@@ -93,20 +92,10 @@ func WithMeterProvider(provider metric.MeterProvider) Option {
})
}
// WithPublicEndpoint configures the Handler to link the span with an incoming
// span context. If this option is not provided, then the association is a child
// association instead of a link.
func WithPublicEndpoint() Option {
return optionFunc(func(c *config) {
c.PublicEndpoint = true
})
}
// WithPublicEndpointFn runs with every request, and allows conditionally
// configuring the Handler to link the span with an incoming span context. If
// this option is not provided or returns false, then the association is a
// child association instead of a link.
// Note: WithPublicEndpoint takes precedence over WithPublicEndpointFn.
func WithPublicEndpointFn(fn func(*http.Request) bool) Option {
return optionFunc(func(c *config) {
c.PublicEndpointFn = fn
@@ -143,11 +132,13 @@ func WithFilter(f Filter) Option {
})
}
type event int
// Event represents message event types for [WithMessageEvents].
type Event int
// Different types of events that can be recorded, see WithMessageEvents.
const (
ReadEvents event = iota
unspecifiedEvents Event = iota
ReadEvents
WriteEvents
)
@@ -160,7 +151,7 @@ const (
// using the ReadBytesKey
// - WriteEvents: Record the number of bytes written after every http.ResponeWriter.Write
// using the WriteBytesKey
func WithMessageEvents(events ...event) Option {
func WithMessageEvents(events ...Event) Option {
return optionFunc(func(c *config) {
for _, e := range events {
switch e {
@@ -203,6 +194,9 @@ func WithServerName(server string) Option {
// WithMetricAttributesFn returns an Option to set a function that maps an HTTP request to a slice of attribute.KeyValue.
// These attributes will be included in metrics for every request.
//
// Deprecated: WithMetricAttributesFn is deprecated and will be removed in a
// future release. Use [Labeler] instead.
func WithMetricAttributesFn(metricAttributesFn func(r *http.Request) []attribute.KeyValue) Option {
return optionFunc(func(c *config) {
c.MetricAttributesFn = metricAttributesFn
@@ -2,6 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
// Package otelhttp provides an http.Handler and functions that are intended
// to be used to add tracing by wrapping existing handlers (with Handler) and
// routes WithRouteTag.
// to be used to add tracing by wrapping existing handlers.
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
@@ -29,7 +29,6 @@ type middleware struct {
writeEvent bool
filters []Filter
spanNameFormatter func(string, *http.Request) string
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
metricAttributesFn func(*http.Request) []attribute.KeyValue
@@ -77,7 +76,6 @@ func (h *middleware) configure(c *config) {
h.writeEvent = c.WriteEvent
h.filters = c.Filters
h.spanNameFormatter = c.SpanNameFormatter
h.publicEndpoint = c.PublicEndpoint
h.publicEndpointFn = c.PublicEndpointFn
h.server = c.ServerName
h.semconv = semconv.NewHTTPServer(c.Meter)
@@ -102,7 +100,7 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
}
opts = append(opts, h.spanStartOptions...)
if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
if h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx)) {
opts = append(opts, trace.WithNewRoot())
// Linking incoming span context if any for public endpoint.
if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
@@ -186,30 +184,26 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
statusCode := rww.StatusCode()
bytesWritten := rww.BytesWritten()
span.SetStatus(h.semconv.Status(statusCode))
bytesRead := bw.BytesRead()
span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
StatusCode: statusCode,
ReadBytes: bw.BytesRead(),
ReadBytes: bytesRead,
ReadError: bw.Error(),
WriteBytes: bytesWritten,
WriteError: rww.Error(),
})...)
// 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: metricAttributes,
ServerName: h.server,
ResponseSize: bytesWritten,
MetricAttributes: semconv.MetricAttributes{
Req: r,
StatusCode: statusCode,
AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
},
MetricData: semconv.MetricData{
RequestSize: bw.BytesRead(),
ElapsedTime: elapsedTime,
RequestSize: bytesRead,
RequestDuration: time.Since(requestStartTime),
},
})
}
@@ -221,18 +215,3 @@ func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.Ke
}
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 {
attr := semconv.NewHTTPServer(nil).Route(route)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attr)
labeler, _ := LabelerFromContext(r.Context())
labeler.Add(attr)
h.ServeHTTP(w, r)
})
}
@@ -61,7 +61,7 @@ func (w *RespWriterWrapper) Write(p []byte) (int, error) {
// WriteHeader persists initial statusCode for span attribution.
// All calls to WriteHeader will be propagated to the underlying ResponseWriter
// and will persist the statusCode from the first call.
// and will persist the statusCode from the first call (except for informational response status codes).
// Blocking consecutive calls to WriteHeader alters expected behavior and will
// remove warning logs from net/http where developers will notice incorrect handler implementations.
func (w *RespWriterWrapper) WriteHeader(statusCode int) {
@@ -77,6 +77,13 @@ func (w *RespWriterWrapper) WriteHeader(statusCode int) {
// parent method.
func (w *RespWriterWrapper) writeHeader(statusCode int) {
if !w.wroteHeader {
// Ignore informational response status codes.
// Based on https://github.com/golang/go/blob/go1.24.1/src/net/http/server.go#L1216
if statusCode >= 100 && statusCode <= 199 && statusCode != http.StatusSwitchingProtocols {
w.ResponseWriter.WriteHeader(statusCode)
return
}
w.wroteHeader = true
w.statusCode = statusCode
}
@@ -0,0 +1,291 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/client.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"context"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
)
type HTTPClient struct {
requestBodySize httpconv.ClientRequestBodySize
requestDuration httpconv.ClientRequestDuration
}
func NewHTTPClient(meter metric.Meter) HTTPClient {
client := HTTPClient{}
var err error
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
handleErr(err)
client.requestDuration, err = httpconv.NewClientRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return client
}
func (n HTTPClient) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
/*
below attributes are returned:
- http.request.method
- http.request.method.original
- url.full
- server.address
- server.port
- network.protocol.name
- network.protocol.version
*/
numOfAttributes := 3 // URL, server address, proto, and method.
var urlHost string
if req.URL != nil {
urlHost = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if eligiblePort > 0 {
numOfAttributes++
}
useragent := req.UserAgent()
if useragent != "" {
numOfAttributes++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
numOfAttributes++
}
if protoVersion != "" {
numOfAttributes++
}
method, originalMethod := n.method(req.Method)
if originalMethod != (attribute.KeyValue{}) {
numOfAttributes++
}
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
attrs = append(attrs, method)
if originalMethod != (attribute.KeyValue{}) {
attrs = append(attrs, originalMethod)
}
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, semconv.URLFull(u))
attrs = append(attrs, semconv.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconv.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
/*
below attributes are returned:
- http.response.status_code
- error.type
*/
var count int
if resp.StatusCode > 0 {
count++
}
if isErrorStatusCode(resp.StatusCode) {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
if resp.StatusCode > 0 {
attrs = append(attrs, semconv.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconv.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n HTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 2
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconv.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconv.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
return attributes
}
type MetricOpts struct {
measurement metric.MeasurementOption
addOptions metric.AddOption
}
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
return o.measurement
}
func (o MetricOpts) AddOptions() metric.AddOption {
return o.addOptions
}
func (n HTTPClient) MetricOptions(ma MetricAttributes) MetricOpts {
attributes := n.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
return MetricOpts{
measurement: set,
addOptions: set,
}
}
func (n HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts MetricOpts) {
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
defer func() {
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}()
*recordOpts = append(*recordOpts, opts.MeasurementOption())
n.requestBodySize.Inst().Record(ctx, md.RequestSize, *recordOpts...)
n.requestDuration.Inst().Record(ctx, durationToSeconds(md.RequestDuration), *recordOpts...)
}
// TraceAttributes returns attributes for httptrace.
func (n HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconv.ServerAddress(host),
}
}
func (n HTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconv.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconv.URLScheme("https")
}
return semconv.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}
@@ -1,248 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/env.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.37.0/httpconv"
)
// OTelSemConvStabilityOptIn is an environment variable.
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
requestBodySizeHistogram httpconv.ServerRequestBodySize
responseBodySizeHistogram httpconv.ServerResponseBodySize
requestDurationHistogram httpconv.ServerRequestDuration
}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
return []attribute.KeyValue{
CurrentHTTPServer{}.NetworkTransportAttr(network),
}
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
return CurrentHTTPServer{}.ResponseTraceAttrs(resp)
}
// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
return CurrentHTTPServer{}.Route(route)
}
// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (s HTTPServer) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
AdditionalAttributes []attribute.KeyValue
}
type MetricData struct {
RequestSize int64
// The request duration, in milliseconds
ElapsedTime float64
}
var (
metricAddOptionPool = &sync.Pool{
New: func() any {
return &[]metric.AddOption{}
},
}
metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
)
func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
s.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
s.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
// hasOptIn returns true if the comma-separated version string contains the
// exact optIn value.
func hasOptIn(version, optIn string) bool {
for _, v := range strings.Split(version, ",") {
if strings.TrimSpace(v) == optIn {
return true
}
}
return false
}
func NewHTTPServer(meter metric.Meter) HTTPServer {
server := HTTPServer{}
var err error
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
handleErr(err)
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
handleErr(err)
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
handleErr(err)
return server
}
type HTTPClient struct {
requestBodySize httpconv.ClientRequestBodySize
requestDuration httpconv.ClientRequestDuration
}
func NewHTTPClient(meter metric.Meter) HTTPClient {
client := HTTPClient{}
var err error
client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter)
handleErr(err)
client.requestDuration, err = httpconv.NewClientRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
)
handleErr(err)
return client
}
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
return CurrentHTTPClient{}.RequestTraceAttrs(req)
}
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
return CurrentHTTPClient{}.ResponseTraceAttrs(resp)
}
func (c HTTPClient) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 400 {
return codes.Error, ""
}
return codes.Unset, ""
}
func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
return CurrentHTTPClient{}.ErrorType(err)
}
type MetricOpts struct {
measurement metric.MeasurementOption
addOptions metric.AddOption
}
func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
return o.measurement
}
func (o MetricOpts) AddOptions() metric.AddOption {
return o.addOptions
}
func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
opts := map[string]MetricOpts{}
attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
set := metric.WithAttributeSet(attribute.NewSet(attributes...))
opts["new"] = MetricOpts{
measurement: set,
addOptions: set,
}
return opts
}
func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())
}
func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return CurrentHTTPClient{}.TraceAttributes(host)
}
@@ -6,10 +6,10 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/
// Generate semconv package:
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=bench_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=common_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=env_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconv_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/server.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/server_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=server_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/client_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=client_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconvtest_test.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go
//go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go
@@ -1,517 +0,0 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/httpconv.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"fmt"
"net/http"
"reflect"
"slices"
"strconv"
"strings"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
type CurrentHTTPServer struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
peer, peerPort := SplitHostPort(req.RemoteAddr)
if peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}
useragent := req.UserAgent()
if useragent != "" {
count++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
route := httpRoute(req.Pattern)
if route != "" {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconvNew.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconvNew.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)
}
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconvNew.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
switch network {
case "tcp", "tcp4", "tcp6":
return semconvNew.NetworkTransportTCP
case "udp", "udp4", "udp6":
return semconvNew.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
return semconvNew.NetworkTransportUnix
default:
return semconvNew.NetworkTransportPipe
}
}
func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}
func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
if https {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
// response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will
// be omitted.
func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int
if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}
attributes := make([]attribute.KeyValue, 0, count)
if resp.ReadBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconvNew.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n CurrentHTTPServer) Route(route string) attribute.KeyValue {
return semconvNew.HTTPRoute(route)
}
func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconvNew.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconvNew.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
type CurrentHTTPClient struct{}
// RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
/*
below attributes are returned:
- http.request.method
- http.request.method.original
- url.full
- server.address
- server.port
- network.protocol.name
- network.protocol.version
*/
numOfAttributes := 3 // URL, server address, proto, and method.
var urlHost string
if req.URL != nil {
urlHost = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if eligiblePort > 0 {
numOfAttributes++
}
useragent := req.UserAgent()
if useragent != "" {
numOfAttributes++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
numOfAttributes++
}
if protoVersion != "" {
numOfAttributes++
}
method, originalMethod := n.method(req.Method)
if originalMethod != (attribute.KeyValue{}) {
numOfAttributes++
}
attrs := make([]attribute.KeyValue, 0, numOfAttributes)
attrs = append(attrs, method)
if originalMethod != (attribute.KeyValue{}) {
attrs = append(attrs, originalMethod)
}
var u string
if req.URL != nil {
// Remove any username/password info that may be in the URL.
userinfo := req.URL.User
req.URL.User = nil
u = req.URL.String()
// Restore any username/password info that was removed.
req.URL.User = userinfo
}
attrs = append(attrs, semconvNew.URLFull(u))
attrs = append(attrs, semconvNew.ServerAddress(requestHost))
if eligiblePort > 0 {
attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
}
return attrs
}
// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
/*
below attributes are returned:
- http.response.status_code
- error.type
*/
var count int
if resp.StatusCode > 0 {
count++
}
if isErrorStatusCode(resp.StatusCode) {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
if resp.StatusCode > 0 {
attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
}
if isErrorStatusCode(resp.StatusCode) {
errorType := strconv.Itoa(resp.StatusCode)
attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
}
return attrs
}
func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
t := reflect.TypeOf(err)
var value string
if t.PkgPath() == "" && t.Name() == "" {
// Likely a builtin type.
value = t.String()
} else {
value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
if value == "" {
return semconvNew.ErrorTypeOther
}
return semconvNew.ErrorTypeKey.String(value)
}
func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconvNew.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconvNew.HTTPRequestMethodGet, orig
}
func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 2
var h string
if req.URL != nil {
h = req.URL.Host
}
var requestHost string
var requestPort int
for _, hostport := range []string{h, req.Header.Get("Host")} {
requestHost, requestPort = SplitHostPort(hostport)
if requestHost != "" || requestPort > 0 {
break
}
}
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
if port > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
semconvNew.ServerAddress(requestHost),
n.scheme(req),
)
if port > 0 {
attributes = append(attributes, semconvNew.ServerPort(port))
}
if protoName != "" {
attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
}
return attributes
}
// TraceAttributes returns attributes for httptrace.
func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
return []attribute.KeyValue{
semconvNew.ServerAddress(host),
}
}
func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
if req.URL != nil && req.URL.Scheme != "" {
return semconvNew.URLScheme(req.URL.Scheme)
}
if req.TLS != nil {
return semconvNew.URLScheme("https")
}
return semconvNew.URLScheme("http")
}
func isErrorStatusCode(code int) bool {
return code >= 400 || code < 100
}
@@ -0,0 +1,396 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/server.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package semconv provides OpenTelemetry semantic convention types and
// functionality.
package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
import (
"context"
"fmt"
"net/http"
"slices"
"strings"
"sync"
"time"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/httpconv"
)
type RequestTraceAttrsOpts struct {
// If set, this is used as value for the "http.client_ip" attribute.
HTTPClientIP string
}
type ResponseTelemetry struct {
StatusCode int
ReadBytes int64
ReadError error
WriteBytes int64
WriteError error
}
type HTTPServer struct {
requestBodySizeHistogram httpconv.ServerRequestBodySize
responseBodySizeHistogram httpconv.ServerResponseBodySize
requestDurationHistogram httpconv.ServerRequestDuration
}
func NewHTTPServer(meter metric.Meter) HTTPServer {
server := HTTPServer{}
var err error
server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter)
handleErr(err)
server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter)
handleErr(err)
server.requestDurationHistogram, err = httpconv.NewServerRequestDuration(
meter,
metric.WithExplicitBucketBoundaries(
0.005, 0.01, 0.025, 0.05, 0.075, 0.1,
0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10,
),
)
handleErr(err)
return server
}
// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (n HTTPServer) Status(code int) (codes.Code, string) {
if code < 100 || code >= 600 {
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
}
if code >= 500 {
return codes.Error, ""
}
return codes.Unset, ""
}
// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (n HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
count := 3 // ServerAddress, Method, Scheme
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
count++
}
method, methodOriginal := n.method(req.Method)
if methodOriginal != (attribute.KeyValue{}) {
count++
}
scheme := n.scheme(req.TLS != nil)
peer, peerPort := SplitHostPort(req.RemoteAddr)
if peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
count++
if peerPort > 0 {
count++
}
}
useragent := req.UserAgent()
if useragent != "" {
count++
}
// For client IP, use, in order:
// 1. The value passed in the options
// 2. The value in the X-Forwarded-For header
// 3. The peer address
clientIP := opts.HTTPClientIP
if clientIP == "" {
clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
if clientIP == "" {
clientIP = peer
}
}
if clientIP != "" {
count++
}
if req.URL != nil && req.URL.Path != "" {
count++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" && protoName != "http" {
count++
}
if protoVersion != "" {
count++
}
route := httpRoute(req.Pattern)
if route != "" {
count++
}
attrs := make([]attribute.KeyValue, 0, count)
attrs = append(attrs,
semconv.ServerAddress(host),
method,
scheme,
)
if hostPort > 0 {
attrs = append(attrs, semconv.ServerPort(hostPort))
}
if methodOriginal != (attribute.KeyValue{}) {
attrs = append(attrs, methodOriginal)
}
if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
// The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
// file-path that would be interpreted with a sock family.
attrs = append(attrs, semconv.NetworkPeerAddress(peer))
if peerPort > 0 {
attrs = append(attrs, semconv.NetworkPeerPort(peerPort))
}
}
if useragent != "" {
attrs = append(attrs, semconv.UserAgentOriginal(useragent))
}
if clientIP != "" {
attrs = append(attrs, semconv.ClientAddress(clientIP))
}
if req.URL != nil && req.URL.Path != "" {
attrs = append(attrs, semconv.URLPath(req.URL.Path))
}
if protoName != "" && protoName != "http" {
attrs = append(attrs, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attrs = append(attrs, semconv.NetworkProtocolVersion(protoVersion))
}
if route != "" {
attrs = append(attrs, n.Route(route))
}
return attrs
}
func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
attr := semconv.NetworkTransportPipe
switch network {
case "tcp", "tcp4", "tcp6":
attr = semconv.NetworkTransportTCP
case "udp", "udp4", "udp6":
attr = semconv.NetworkTransportUDP
case "unix", "unixgram", "unixpacket":
attr = semconv.NetworkTransportUnix
}
return []attribute.KeyValue{attr}
}
type ServerMetricData struct {
ServerName string
ResponseSize int64
MetricData
MetricAttributes
}
type MetricAttributes struct {
Req *http.Request
StatusCode int
Route string
AdditionalAttributes []attribute.KeyValue
}
type MetricData struct {
RequestSize int64
RequestDuration time.Duration
}
var (
metricRecordOptionPool = &sync.Pool{
New: func() any {
return &[]metric.RecordOption{}
},
}
)
func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes)
o := metric.WithAttributeSet(attribute.NewSet(attributes...))
recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
*recordOpts = append(*recordOpts, o)
n.requestBodySizeHistogram.Inst().Record(ctx, md.RequestSize, *recordOpts...)
n.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...)
n.requestDurationHistogram.Inst().Record(ctx, durationToSeconds(md.RequestDuration), o)
*recordOpts = (*recordOpts)[:0]
metricRecordOptionPool.Put(recordOpts)
}
func (n HTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
if method == "" {
return semconv.HTTPRequestMethodGet, attribute.KeyValue{}
}
if attr, ok := methodLookup[method]; ok {
return attr, attribute.KeyValue{}
}
orig := semconv.HTTPRequestMethodOriginal(method)
if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
return attr, orig
}
return semconv.HTTPRequestMethodGet, orig
}
func (n HTTPServer) scheme(https bool) attribute.KeyValue { //nolint:revive // ignore linter
if https {
return semconv.URLScheme("https")
}
return semconv.URLScheme("http")
}
// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
// response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will
// be omitted.
func (n HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
var count int
if resp.ReadBytes > 0 {
count++
}
if resp.WriteBytes > 0 {
count++
}
if resp.StatusCode > 0 {
count++
}
attributes := make([]attribute.KeyValue, 0, count)
if resp.ReadBytes > 0 {
attributes = append(attributes,
semconv.HTTPRequestBodySize(int(resp.ReadBytes)),
)
}
if resp.WriteBytes > 0 {
attributes = append(attributes,
semconv.HTTPResponseBodySize(int(resp.WriteBytes)),
)
}
if resp.StatusCode > 0 {
attributes = append(attributes,
semconv.HTTPResponseStatusCode(resp.StatusCode),
)
}
return attributes
}
// Route returns the attribute for the route.
func (n HTTPServer) Route(route string) attribute.KeyValue {
return semconv.HTTPRoute(route)
}
func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
num := len(additionalAttributes) + 3
var host string
var p int
if server == "" {
host, p = SplitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = SplitHostPort(server)
if p < 0 {
_, p = SplitHostPort(req.Host)
}
}
hostPort := requiredHTTPPort(req.TLS != nil, p)
if hostPort > 0 {
num++
}
protoName, protoVersion := netProtocol(req.Proto)
if protoName != "" {
num++
}
if protoVersion != "" {
num++
}
if statusCode > 0 {
num++
}
if route != "" {
num++
}
attributes := slices.Grow(additionalAttributes, num)
attributes = append(attributes,
semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
n.scheme(req.TLS != nil),
semconv.ServerAddress(host))
if hostPort > 0 {
attributes = append(attributes, semconv.ServerPort(hostPort))
}
if protoName != "" {
attributes = append(attributes, semconv.NetworkProtocolName(protoName))
}
if protoVersion != "" {
attributes = append(attributes, semconv.NetworkProtocolVersion(protoVersion))
}
if statusCode > 0 {
attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode))
}
if route != "" {
attributes = append(attributes, semconv.HTTPRoute(route))
}
return attributes
}
@@ -11,10 +11,11 @@ import (
"net/http"
"strconv"
"strings"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconvNew "go.opentelemetry.io/otel/semconv/v1.37.0"
semconvNew "go.opentelemetry.io/otel/semconv/v1.40.0"
)
// SplitHostPort splits a network address hostport of the form "host",
@@ -125,3 +126,8 @@ func standardizeHTTPMethod(method string) string {
}
return method
}
func durationToSeconds(d time.Duration) float64 {
// Use floating point division here for higher precision (instead of Seconds method).
return float64(d) / float64(time.Second)
}
@@ -15,6 +15,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
otelsemconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
@@ -102,9 +103,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
}
}
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), t.spanStartOptions...)
if t.clientTrace != nil {
ctx = httptrace.WithClientTrace(ctx, t.clientTrace(ctx))
@@ -117,12 +116,26 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
r = r.Clone(ctx) // According to RoundTripper spec, we shouldn't modify the origin request.
// if request body is nil or NoBody, we don't want to mutate the body as it
// will affect the identity of it in an unforeseeable way because we assert
// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
bw := request.NewBodyWrapper(r.Body, func(int64) {})
if r.Body != nil && r.Body != http.NoBody {
r.Body = bw
var lastBW *request.BodyWrapper // Records the last body wrapper. Can be nil.
maybeWrapBody := func(body io.ReadCloser) io.ReadCloser {
if body == nil || body == http.NoBody {
return body
}
bw := request.NewBodyWrapper(body, func(int64) {})
lastBW = bw
return bw
}
r.Body = maybeWrapBody(r.Body)
if r.GetBody != nil {
originalGetBody := r.GetBody
r.GetBody = func() (io.ReadCloser, error) {
b, err := originalGetBody()
if err != nil {
lastBW = nil // The underlying transport will fail to make a retry request, hence, record no data.
return nil, err
}
return maybeWrapBody(b), nil
}
}
span.SetAttributes(t.semconv.RequestTraceAttrs(r)...)
@@ -130,52 +143,38 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
res, err := t.rt.RoundTrip(r)
// Defer metrics recording function to record the metrics on error or no error.
defer func() {
metricAttributes := semconv.MetricAttributes{
// Record the metrics on error or no error.
statusCode := 0
if err == nil {
statusCode = res.StatusCode
}
var requestSize int64
if lastBW != nil {
requestSize = lastBW.BytesRead()
}
t.semconv.RecordMetrics(
ctx,
semconv.MetricData{
RequestSize: requestSize,
RequestDuration: time.Since(requestStartTime),
},
t.semconv.MetricOptions(semconv.MetricAttributes{
Req: r,
StatusCode: statusCode,
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 {
readRecordFunc := func(int64) {}
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.
// otherwise, record it as an exception
if errType := t.semconv.ErrorType(err); errType.Valid() {
span.SetAttributes(errType)
} else {
span.RecordError(err)
}
span.SetAttributes(otelsemconv.ErrorType(err))
span.SetStatus(codes.Error, err.Error())
span.End()
return res, err
}
readRecordFunc := func(int64) {}
res.Body = newWrappedBody(span, readRecordFunc, res.Body)
// traces
span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...)
span.SetStatus(t.semconv.Status(res.StatusCode))
@@ -229,7 +228,7 @@ func (wb *wrappedBody) Write(p []byte) (int, error) {
// This will not panic given the guard in newWrappedBody.
n, err := wb.body.(io.Writer).Write(p)
if err != nil {
wb.span.RecordError(err)
wb.span.SetAttributes(otelsemconv.ErrorType(err))
wb.span.SetStatus(codes.Error, err.Error())
}
return n, err
@@ -247,7 +246,7 @@ func (wb *wrappedBody) Read(b []byte) (int, error) {
wb.recordBytesRead()
wb.span.End()
default:
wb.span.RecordError(err)
wb.span.SetAttributes(otelsemconv.ErrorType(err))
wb.span.SetStatus(codes.Error, err.Error())
}
return n, err
@@ -4,7 +4,4 @@
package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
// Version is the current release version of the otelhttp instrumentation.
func Version() string {
return "0.63.0"
// This string is updated by the pre_release.sh script during release
}
const Version = "0.67.0"
+1
View File
@@ -194,6 +194,7 @@ linters:
arguments:
- ["ID"] # AllowList
- ["Otel", "Aws", "Gcp"] # DenyList
- - skip-package-name-collision-with-go-std: true
- name: waitgroup-by-value
testifylint:
enable-all: true
+45 -1
View File
@@ -11,6 +11,47 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
<!-- Released section -->
<!-- Don't change this section unless doing release -->
## [1.42.0/0.64.0/0.18.0/0.0.16] 2026-03-06
### Added
- Add `go.opentelemetry.io/otel/semconv/v1.40.0` package.
The package contains semantic conventions from the `v1.40.0` version of the OpenTelemetry Semantic Conventions.
See the [migration documentation](./semconv/v1.40.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.39.0`. (#7985)
- Add `Err` and `SetErr` on `Record` in `go.opentelemetry.io/otel/log` to attach an error and set record exception attributes in `go.opentelemetry.io/otel/log/sdk`. (#7924)
### Changed
- `TracerProvider.ForceFlush` in `go.opentelemetry.io/otel/sdk/trace` joins errors together and continues iteration through SpanProcessors as opposed to returning the first encountered error without attempting exports on subsequent SpanProcessors. (#7856)
### Fixed
- Fix missing `request.GetBody` in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` to correctly handle HTTP2 GOAWAY frame. (#7931)
- Fix semconv v1.39.0 generated metric helpers skipping required attributes when extra attributes were empty. (#7964)
- Preserve W3C TraceFlags bitmask (including the random Trace ID flag) during trace context extraction and injection in `go.opentelemetry.io/otel/propagation`. (#7834)
### Removed
- Drop support for [Go 1.24]. (#7984)
## [1.41.0/0.63.0/0.17.0/0.0.15] 2026-03-02
This release is the last to support [Go 1.24].
The next release will require at least [Go 1.25].
### Added
- Support testing of [Go 1.26]. (#7902)
### Fixed
- Update `Baggage` in `go.opentelemetry.io/otel/propagation` and `Parse` and `New` in `go.opentelemetry.io/otel/baggage` to comply with W3C Baggage specification limits.
`New` and `Parse` now return partial baggage along with an error when limits are exceeded.
Errors from baggage extraction are reported to the global error handler. (#7880)
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#7914)
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#7914)
- Return an error when the endpoint is configured as insecure and with TLS configuration in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#7914)
## [1.40.0/0.62.0/0.16.0] 2026-02-02
### Added
@@ -3535,7 +3576,9 @@ It contains api and sdk for trace and meter.
- CircleCI build CI manifest files.
- CODEOWNERS file to track owners of this project.
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.40.0...HEAD
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.42.0...HEAD
[1.42.0/0.64.0/0.18.0/0.0.16]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.42.0
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
[1.40.0/0.62.0/0.16.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.40.0
[1.39.0/0.61.0/0.15.0/0.0.14]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.39.0
[1.38.0/0.60.0/0.14.0/0.0.13]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.38.0
@@ -3635,6 +3678,7 @@ It contains api and sdk for trace and meter.
<!-- Released section ended -->
[Go 1.26]: https://go.dev/doc/go1.26
[Go 1.25]: https://go.dev/doc/go1.25
[Go 1.24]: https://go.dev/doc/go1.24
[Go 1.23]: https://go.dev/doc/go1.23
+3 -3
View File
@@ -746,8 +746,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
import (
"errors"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
type SDKComponent struct {
@@ -1039,7 +1039,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.39.0/otelconv/metric.go).
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.40.0/otelconv/metric.go).
##### Component Identification
+5 -6
View File
@@ -185,11 +185,10 @@ test-coverage: $(GOCOVMERGE)
.PHONY: benchmark
benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
benchmark/%:
@echo "$(GO) test -run=xxxxxMatchNothingxxxxx -bench=. $*..." \
&& cd $* \
&& $(GO) list ./... \
| grep -v third_party \
| xargs $(GO) test -run=xxxxxMatchNothingxxxxx -bench=.
cd $* && $(GO) test -run='^$$' -bench=. $(ARGS) ./...
print-sharded-benchmarks:
@echo $(OTEL_GO_MOD_DIRS) | jq -cR 'split(" ")'
.PHONY: golangci-lint golangci-lint-fix
golangci-lint-fix: ARGS=--fix
@@ -215,7 +214,7 @@ go-mod-tidy/%: crosslink
&& $(GO) mod tidy -compat=1.21
.PHONY: lint
lint: misspell go-mod-tidy golangci-lint govulncheck
lint: misspell go-mod-tidy golangci-lint
.PHONY: vanity-import-check
vanity-import-check: $(PORTO)
+7 -7
View File
@@ -53,20 +53,20 @@ Currently, this project supports the following environments.
| OS | Go Version | Architecture |
|----------|------------|--------------|
| Ubuntu | 1.26 | amd64 |
| Ubuntu | 1.25 | amd64 |
| Ubuntu | 1.24 | amd64 |
| Ubuntu | 1.26 | 386 |
| Ubuntu | 1.25 | 386 |
| Ubuntu | 1.24 | 386 |
| Ubuntu | 1.26 | arm64 |
| Ubuntu | 1.25 | arm64 |
| Ubuntu | 1.24 | arm64 |
| macOS | 1.26 | amd64 |
| macOS | 1.25 | amd64 |
| macOS | 1.24 | amd64 |
| macOS | 1.26 | arm64 |
| macOS | 1.25 | arm64 |
| macOS | 1.24 | arm64 |
| Windows | 1.26 | amd64 |
| Windows | 1.25 | amd64 |
| Windows | 1.24 | amd64 |
| Windows | 1.26 | 386 |
| Windows | 1.25 | 386 |
| Windows | 1.24 | 386 |
While this project should work for other systems, no compatibility guarantees
are made for those systems currently.
+83 -26
View File
@@ -14,8 +14,7 @@ import (
)
const (
maxMembers = 180
maxBytesPerMembers = 4096
maxMembers = 64
maxBytesPerBaggageString = 8192
listDelimiter = ","
@@ -29,7 +28,6 @@ var (
errInvalidProperty = errors.New("invalid baggage list-member property")
errInvalidMember = errors.New("invalid baggage list-member")
errMemberNumber = errors.New("too many list-members in baggage-string")
errMemberBytes = errors.New("list-member too large")
errBaggageBytes = errors.New("baggage-string too large")
)
@@ -309,10 +307,6 @@ func newInvalidMember() Member {
// an error if the input is invalid according to the W3C Baggage
// specification.
func parseMember(member string) (Member, error) {
if n := len(member); n > maxBytesPerMembers {
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
}
var props properties
keyValue, properties, found := strings.Cut(member, propertyDelimiter)
if found {
@@ -430,6 +424,10 @@ type Baggage struct { //nolint:golint
// New returns a new valid Baggage. It returns an error if it results in a
// Baggage exceeding limits set in that specification.
//
// If the resulting Baggage exceeds the maximum allowed members or bytes,
// members are dropped until the limits are satisfied and an error is returned
// along with the partial result.
//
// It expects all the provided members to have already been validated.
func New(members ...Member) (Baggage, error) {
if len(members) == 0 {
@@ -441,7 +439,6 @@ func New(members ...Member) (Baggage, error) {
if !m.hasData {
return Baggage{}, errInvalidMember
}
// OpenTelemetry resolves duplicates by last-one-wins.
b[m.key] = baggage.Item{
Value: m.value,
@@ -449,17 +446,42 @@ func New(members ...Member) (Baggage, error) {
}
}
// Check member numbers after deduplication.
var truncateErr error
// Check member count after deduplication.
if len(b) > maxMembers {
return Baggage{}, errMemberNumber
truncateErr = errors.Join(truncateErr, errMemberNumber)
for k := range b {
if len(b) <= maxMembers {
break
}
delete(b, k)
}
}
bag := Baggage{b}
if n := len(bag.String()); n > maxBytesPerBaggageString {
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
// Check byte size and drop members if necessary.
totalBytes := 0
first := true
for k := range b {
m := Member{
key: k,
value: b[k].Value,
properties: fromInternalProperties(b[k].Properties),
}
memberSize := len(m.String())
if !first {
memberSize++ // comma separator
}
if totalBytes+memberSize > maxBytesPerBaggageString {
truncateErr = errors.Join(truncateErr, fmt.Errorf("%w: %d", errBaggageBytes, totalBytes+memberSize))
delete(b, k)
continue
}
totalBytes += memberSize
first = false
}
return bag, nil
return Baggage{b}, truncateErr
}
// Parse attempts to decode a baggage-string from the passed string. It
@@ -470,36 +492,71 @@ func New(members ...Member) (Baggage, error) {
// defined (reading left-to-right) will be the only one kept. This diverges
// from the W3C Baggage specification which allows duplicate list-members, but
// conforms to the OpenTelemetry Baggage specification.
//
// If the baggage-string exceeds the maximum allowed members (64) or bytes
// (8192), members are dropped until the limits are satisfied and an error is
// returned along with the partial result.
//
// Invalid members are skipped and the error is returned along with the
// partial result containing the valid members.
func Parse(bStr string) (Baggage, error) {
if bStr == "" {
return Baggage{}, nil
}
if n := len(bStr); n > maxBytesPerBaggageString {
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
}
b := make(baggage.List)
sizes := make(map[string]int) // Track per-key byte sizes
var totalBytes int
var truncateErr error
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
// Check member count limit.
if len(b) >= maxMembers {
truncateErr = errors.Join(truncateErr, errMemberNumber)
break
}
m, err := parseMember(memberStr)
if err != nil {
return Baggage{}, err
truncateErr = errors.Join(truncateErr, err)
continue // skip invalid member, keep processing
}
// Check byte size limit.
// Account for comma separator between members.
memberBytes := len(m.String())
_, existingKey := b[m.key]
if !existingKey && len(b) > 0 {
memberBytes++ // comma separator only for new keys
}
// Calculate new totalBytes if we add/overwrite this key
var newTotalBytes int
if oldSize, exists := sizes[m.key]; exists {
// Overwriting existing key: subtract old size, add new size
newTotalBytes = totalBytes - oldSize + memberBytes
} else {
// New key
newTotalBytes = totalBytes + memberBytes
}
if newTotalBytes > maxBytesPerBaggageString {
truncateErr = errors.Join(truncateErr, errBaggageBytes)
break
}
// OpenTelemetry resolves duplicates by last-one-wins.
b[m.key] = baggage.Item{
Value: m.value,
Properties: m.properties.asInternal(),
}
sizes[m.key] = memberBytes
totalBytes = newTotalBytes
}
// OpenTelemetry does not allow for duplicate list-members, but the W3C
// specification does. Now that we have deduplicated, ensure the baggage
// does not exceed list-member limits.
if len(b) > maxMembers {
return Baggage{}, errMemberNumber
if len(b) == 0 {
return Baggage{}, truncateErr
}
return Baggage{b}, nil
return Baggage{b}, truncateErr
}
// Member returns the baggage list-member identified by key.
+1 -1
View File
@@ -1,4 +1,4 @@
# This is a renovate-friendly source of Docker images.
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
FROM otel/weaver:v0.20.0@sha256:fa4f1c6954ecea78ab1a4e865bd6f5b4aaba80c1896f9f4a11e2c361d04e197e AS weaver
FROM otel/weaver:v0.21.2@sha256:2401de985c38bdb98b43918e2f43aa36b2afed4aa5669ac1c1de0a17301cd36d AS weaver
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
@@ -151,7 +151,7 @@ func (c *client) exportContext(parent context.Context) (context.Context, context
if c.exportTimeout > 0 {
ctx, cancel = context.WithTimeoutCause(parent, c.exportTimeout, errors.New("exporter export timeout"))
} else {
ctx, cancel = context.WithCancel(parent)
ctx, cancel = context.WithCancel(parent) //nolint:gosec // cancel is handled by the caller.
}
if c.metadata.Len() > 0 {
@@ -5,5 +5,5 @@ package otlpmetricgrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
// Version is the current release version of the OpenTelemetry OTLP over gRPC metrics exporter in use.
func Version() string {
return "1.40.0"
return "1.42.0"
}
@@ -52,8 +52,14 @@ var ourTransport = &http.Transport{
ExpectContinueTimeout: 1 * time.Second,
}
var errInsecureEndpointWithTLS = errors.New("insecure HTTP endpoint cannot use TLS client configuration")
// newClient creates a new HTTP metric client.
func newClient(cfg oconf.Config) (*client, error) {
if cfg.Metrics.Insecure && cfg.Metrics.TLSCfg != nil {
return nil, errInsecureEndpointWithTLS
}
httpClient := cfg.Metrics.HTTPClient
if httpClient == nil {
httpClient = &http.Client{
@@ -146,6 +152,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
}
request.reset(iCtx)
// nolint:gosec // URL is constructed from validated OTLP endpoint configuration
resp, err := c.httpClient.Do(request.Request)
var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.Temporary() {
@@ -236,6 +243,7 @@ func (c *client) newRequest(ctx context.Context, body []byte) (request, error) {
case NoCompression:
r.ContentLength = int64(len(body))
req.bodyReader = bodyReader(body)
req.GetBody = bodyReaderErr(body)
case GzipCompression:
// Ensure the content length is not used.
r.ContentLength = -1
@@ -256,6 +264,7 @@ func (c *client) newRequest(ctx context.Context, body []byte) (request, error) {
}
req.bodyReader = bodyReader(b.Bytes())
req.GetBody = bodyReaderErr(body)
}
return req, nil
@@ -268,6 +277,13 @@ func bodyReader(buf []byte) func() io.ReadCloser {
}
}
// bodyReaderErr returns a closure returning a new reader for buf.
func bodyReaderErr(buf []byte) func() (io.ReadCloser, error) {
return func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(buf)), nil
}
}
// request wraps an http.Request with a resettable body reader.
type request struct {
*http.Request
@@ -5,5 +5,5 @@ package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpme
// Version is the current release version of the OpenTelemetry OTLP over HTTP/protobuf metrics exporter in use.
func Version() string {
return "1.40.0"
return "1.42.0"
}
@@ -62,7 +62,7 @@ func NewClient(opts ...Option) otlptrace.Client {
func newClient(opts ...Option) *client {
cfg := otlpconfig.NewGRPCConfig(asGRPCOptions(opts)...)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // cancel called in client shutdown.
c := &client{
endpoint: cfg.Traces.Endpoint,
@@ -248,7 +248,7 @@ func (c *client) exportContext(parent context.Context) (context.Context, context
if c.exportTimeout > 0 {
ctx, cancel = context.WithTimeoutCause(parent, c.exportTimeout, errors.New("exporter export timeout"))
} else {
ctx, cancel = context.WithCancel(parent)
ctx, cancel = context.WithCancel(parent) //nolint:gosec // cancel called by caller when export is complete.
}
if c.metadata.Len() > 0 {
@@ -18,8 +18,8 @@ import (
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
const (
@@ -208,10 +208,12 @@ func BaseAttrs(id int64, target string) []attribute.KeyValue {
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
start := time.Now()
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.addOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
if i.inflightSpans.Enabled(ctx) {
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.addOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
}
return ExportOp{
ctx: ctx,
@@ -244,14 +246,18 @@ func (e ExportOp) End(err error, code codes.Code) {
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, e.inst.addOpt)
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
if e.inst.inflightSpans.Enabled(e.ctx) {
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
}
success := successful(e.nSpans, err)
// Record successfully exported spans, even if the value is 0 which are
// meaningful to distribution aggregations.
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
if e.inst.exportedSpans.Enabled(e.ctx) {
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
}
if err != nil {
if err != nil && e.inst.exportedSpans.Enabled(e.ctx) {
attrs := get[attribute.KeyValue](measureAttrsPool)
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, e.inst.attrs...)
@@ -266,12 +272,14 @@ func (e ExportOp) End(err error, code codes.Code) {
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
}
recOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recOpt)
*recOpt = append(*recOpt, e.inst.recordOption(err, code))
if e.inst.opDuration.Enabled(e.ctx) {
recOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recOpt)
*recOpt = append(*recOpt, e.inst.recordOption(err, code))
d := time.Since(e.start).Seconds()
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
d := time.Since(e.start).Seconds()
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
}
}
// recordOption returns a RecordOption with attributes representing the
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
// exporter in use.
const Version = "1.40.0"
const Version = "1.42.0"
@@ -56,6 +56,8 @@ var ourTransport = &http.Transport{
ExpectContinueTimeout: 1 * time.Second,
}
var errInsecureEndpointWithTLS = errors.New("insecure HTTP endpoint cannot use TLS client configuration")
type client struct {
name string
cfg otlpconfig.SignalConfig
@@ -110,6 +112,10 @@ func NewClient(opts ...Option) otlptrace.Client {
// Start does nothing in a HTTP client.
func (c *client) Start(ctx context.Context) error {
if c.cfg.Insecure && c.cfg.TLSCfg != nil {
return errInsecureEndpointWithTLS
}
// Initialize the instrumentation if not already done.
//
// Initialize here instead of NewClient to allow any errors to be passed
@@ -174,6 +180,7 @@ func (d *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.Resourc
}
request.reset(ctx)
// nolint:gosec // URL is constructed from validated OTLP endpoint configuration
resp, err := d.client.Do(request.Request)
var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.Temporary() {
@@ -23,8 +23,8 @@ import (
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/x"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
const (
@@ -261,10 +261,12 @@ func parseIP(ip string) string {
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
start := time.Now()
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.addOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
if i.inflightSpans.Enabled(ctx) {
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.addOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
}
return ExportOp{
ctx: ctx,
@@ -299,14 +301,18 @@ func (e ExportOp) End(err error, status int) {
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, e.inst.addOpt)
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
if e.inst.inflightSpans.Enabled(e.ctx) {
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
}
success := successful(e.nSpans, err)
// Record successfully exported spans, even if the value is 0 which are
// meaningful to distribution aggregations.
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
if e.inst.exportedSpans.Enabled(e.ctx) {
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
}
if err != nil {
if err != nil && e.inst.exportedSpans.Enabled(e.ctx) {
attrs := get[attribute.KeyValue](measureAttrsPool)
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, e.inst.attrs...)
@@ -321,12 +327,14 @@ func (e ExportOp) End(err error, status int) {
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
}
recOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recOpt)
*recOpt = append(*recOpt, e.inst.recordOption(err, status))
if e.inst.opDuration.Enabled(e.ctx) {
recOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recOpt)
*recOpt = append(*recOpt, e.inst.recordOption(err, status))
d := time.Since(e.start).Seconds()
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
d := time.Since(e.start).Seconds()
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
}
}
// recordOption returns a RecordOption with attributes representing the
@@ -5,4 +5,4 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/ot
// Version is the current release version of the OpenTelemetry OTLP HTTP trace
// exporter in use.
const Version = "1.40.0"
const Version = "1.42.0"
+1 -1
View File
@@ -5,5 +5,5 @@ package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
func Version() string {
return "1.40.0"
return "1.42.0"
}
@@ -0,0 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package internal provides internal functionality for the stdouttrace
// package.
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
//go:generate gotmpl --body=../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter\" }" --out=counter/counter.go
//go:generate gotmpl --body=../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
//go:generate gotmpl --body=../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\" }" --out=x/x.go
//go:generate gotmpl --body=../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
@@ -0,0 +1,230 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package observ provides experimental observability instrumentation
// for the stdout trace exporter.
package observ // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
import (
"context"
"errors"
"fmt"
"sync"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
const (
// ComponentType uniquely identifies the OpenTelemetry Exporter component
// being instrumented.
//
// The STDOUT trace exporter is not a standardized OTel component type, so
// it uses the Go package prefixed type name to ensure uniqueness and
// identity.
ComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
// ScopeName is the unique name of the meter used for instrumentation.
ScopeName = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
// SchemaURL is the schema URL of the metrics produced by this
// instrumentation.
SchemaURL = semconv.SchemaURL
// Version is the current version of this instrumentation.
//
// This matches the version of the exporter.
Version = internal.Version
)
var (
measureAttrsPool = &sync.Pool{
New: func() any {
// "component.name" + "component.type" + "error.type"
const n = 1 + 1 + 1
s := make([]attribute.KeyValue, 0, n)
// Return a pointer to a slice instead of a slice itself
// to avoid allocations on every call.
return &s
},
}
addOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.AddOption, 0, n)
return &o
},
}
recordOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.RecordOption, 0, n)
return &o
},
}
)
func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) }
func put[T any](p *sync.Pool, s *[]T) {
*s = (*s)[:0] // Reset.
p.Put(s)
}
func ComponentName(id int64) string {
return fmt.Sprintf("%s/%d", ComponentType, id)
}
// Instrumentation is experimental instrumentation for the exporter.
type Instrumentation struct {
inflightSpans metric.Int64UpDownCounter
exportedSpans metric.Int64Counter
opDuration metric.Float64Histogram
attrs []attribute.KeyValue
setOpt metric.MeasurementOption
}
// NewInstrumentation returns instrumentation for a STDOUT trace exporter with
// the provided ID using the global MeterProvider.
//
// If the experimental observability is disabled, nil is returned.
func NewInstrumentation(id int64) (*Instrumentation, error) {
if !x.Observability.Enabled() {
return nil, nil
}
i := &Instrumentation{
attrs: []attribute.KeyValue{
semconv.OTelComponentName(ComponentName(id)),
semconv.OTelComponentTypeKey.String(ComponentType),
},
}
s := attribute.NewSet(i.attrs...)
i.setOpt = metric.WithAttributeSet(s)
mp := otel.GetMeterProvider()
m := mp.Meter(
ScopeName,
metric.WithInstrumentationVersion(Version),
metric.WithSchemaURL(SchemaURL),
)
var err error
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
if e != nil {
e = fmt.Errorf("failed to create span inflight metric: %w", e)
err = errors.Join(err, e)
}
i.inflightSpans = inflightSpans.Inst()
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
if e != nil {
e = fmt.Errorf("failed to create span exported metric: %w", e)
err = errors.Join(err, e)
}
i.exportedSpans = exportedSpans.Inst()
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
if e != nil {
e = fmt.Errorf("failed to create operation duration metric: %w", e)
err = errors.Join(err, e)
}
i.opDuration = opDuration.Inst()
return i, err
}
// ExportSpans instruments the ExportSpans method of the exporter. It returns a
// function that needs to be deferred so it is called when the method returns.
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
start := time.Now()
if i.inflightSpans.Enabled(ctx) {
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, i.setOpt)
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
}
return ExportOp{
ctx: ctx,
start: start,
nSpans: int64(nSpans),
inst: i,
}
}
// ExportOp is an in-progress ExportSpans operation.
type ExportOp struct {
ctx context.Context
start time.Time
nSpans int64
inst *Instrumentation
}
// End ends the ExportSpans operation, recording its success and duration.
//
// The success parameter indicates how many spans were successfully exported.
// The err parameter indicates whether the operation failed. If err is not nil,
// the number of failed spans (nSpans - success) is also recorded.
func (e ExportOp) End(success int64, err error) {
inflightSpansEnable := e.inst.inflightSpans.Enabled(e.ctx)
exportedSpansEnable := e.inst.exportedSpans.Enabled(e.ctx)
opDurationEnable := e.inst.opDuration.Enabled(e.ctx)
if !inflightSpansEnable && !exportedSpansEnable && !opDurationEnable {
return
}
addOpt := get[metric.AddOption](addOptPool)
defer put(addOptPool, addOpt)
*addOpt = append(*addOpt, e.inst.setOpt)
if inflightSpansEnable {
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
}
// Record the success and duration of the operation.
//
// Do not exclude 0 values, as they are valid and indicate no spans
// were exported which is meaningful for certain aggregations.
if exportedSpansEnable {
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
}
mOpt := e.inst.setOpt
if err != nil && exportedSpansEnable {
attrs := get[attribute.KeyValue](measureAttrsPool)
defer put(measureAttrsPool, attrs)
*attrs = append(*attrs, e.inst.attrs...)
*attrs = append(*attrs, semconv.ErrorType(err))
// Do not inefficiently make a copy of attrs by using
// WithAttributes instead of WithAttributeSet.
set := attribute.NewSet(*attrs...)
mOpt = metric.WithAttributeSet(set)
// Reset addOpt with new attribute set.
*addOpt = append((*addOpt)[:0], mOpt)
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
}
if opDurationEnable {
recordOpt := get[metric.RecordOption](recordOptPool)
defer put(recordOptPool, recordOpt)
*recordOpt = append(*recordOpt, mOpt)
e.inst.opDuration.Record(e.ctx, time.Since(e.start).Seconds(), *recordOpt...)
}
}
@@ -0,0 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
// Version is the current release version of the OpenTelemetry stdouttrace
// exporter in use.
const Version = "1.42.0"
@@ -8,13 +8,13 @@ See the [Compatibility and Stability](#compatibility-and-stability) section for
## Features
- [Self-Observability](#self-observability)
- [Observability](#observability)
### Self-Observability
### Observability
The `stdouttrace` exporter provides a self-observability feature that allows you to monitor the SDK itself.
The `stdouttrace` exporter can be configured to provide observability about itself using OpenTelemetry metrics.
To opt-in, set the environment variable `OTEL_GO_X_SELF_OBSERVABILITY` to `true`.
To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`.
When enabled, the SDK will create the following metrics using the global `MeterProvider`:
@@ -0,0 +1,23 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/stdout/stdouttrace].
package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
import "strings"
// Observability is an experimental feature flag that determines if exporter
// observability metrics are enabled.
//
// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
// will also enable this).
var Observability = newFeature(
[]string{"OBSERVABILITY", "SELF_OBSERVABILITY"},
func(v string) (string, bool) {
if strings.EqualFold(v, "true") {
return v, true
}
return "", false
},
)
+18 -23
View File
@@ -1,3 +1,6 @@
// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/x/x.go.tmpl
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
@@ -6,40 +9,30 @@ package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/inter
import (
"os"
"strings"
)
// SelfObservability is an experimental feature flag that determines if SDK
// self-observability metrics are enabled.
//
// To enable this feature set the OTEL_GO_X_SELF_OBSERVABILITY environment variable
// to the case-insensitive string value of "true" (i.e. "True" and "TRUE"
// will also enable this).
var SelfObservability = newFeature("SELF_OBSERVABILITY", func(v string) (string, bool) {
if strings.EqualFold(v, "true") {
return v, true
}
return "", false
})
// Feature is an experimental feature control flag. It provides a uniform way
// to interact with these feature flags and parse their values.
type Feature[T any] struct {
key string
keys []string
parse func(v string) (T, bool)
}
func newFeature[T any](suffix string, parse func(string) (T, bool)) Feature[T] {
func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] {
const envKeyRoot = "OTEL_GO_X_"
keys := make([]string, 0, len(suffix))
for _, s := range suffix {
keys = append(keys, envKeyRoot+s)
}
return Feature[T]{
key: envKeyRoot + suffix,
keys: keys,
parse: parse,
}
}
// Key returns the environment variable key that needs to be set to enable the
// Keys returns the environment variable keys that can be set to enable the
// feature.
func (f Feature[T]) Key() string { return f.key }
func (f Feature[T]) Keys() []string { return f.keys }
// Lookup returns the user configured value for the feature and true if the
// user has enabled the feature. Otherwise, if the feature is not enabled, a
@@ -49,11 +42,13 @@ func (f Feature[T]) Lookup() (v T, ok bool) {
//
// > The SDK MUST interpret an empty value of an environment variable the
// > same way as when the variable is unset.
vRaw := os.Getenv(f.key)
if vRaw == "" {
return v, ok
for _, key := range f.keys {
vRaw := os.Getenv(key)
if vRaw != "" {
return f.parse(vRaw)
}
}
return f.parse(vRaw)
return v, ok
}
// Enabled reports whether the feature is enabled.
+7 -141
View File
@@ -11,23 +11,12 @@ import (
"sync"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
)
// otelComponentType is a name identifying the type of the OpenTelemetry
// component. It is not a standardized OTel component type, so it uses the
// Go package prefixed type name to ensure uniqueness and identity.
const otelComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
var zeroTime time.Time
var _ trace.SpanExporter = &Exporter{}
@@ -46,39 +35,8 @@ func New(options ...Option) (*Exporter, error) {
timestamps: cfg.Timestamps,
}
if !x.SelfObservability.Enabled() {
return exporter, nil
}
exporter.selfObservabilityEnabled = true
exporter.selfObservabilityAttrs = []attribute.KeyValue{
semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())),
semconv.OTelComponentTypeKey.String(otelComponentType),
}
s := attribute.NewSet(exporter.selfObservabilityAttrs...)
exporter.selfObservabilitySetOpt = metric.WithAttributeSet(s)
mp := otel.GetMeterProvider()
m := mp.Meter(
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace",
metric.WithInstrumentationVersion(sdk.Version()),
metric.WithSchemaURL(semconv.SchemaURL),
)
var err, e error
if exporter.spanInflightMetric, e = otelconv.NewSDKExporterSpanInflight(m); e != nil {
e = fmt.Errorf("failed to create span inflight metric: %w", e)
err = errors.Join(err, e)
}
if exporter.spanExportedMetric, e = otelconv.NewSDKExporterSpanExported(m); e != nil {
e = fmt.Errorf("failed to create span exported metric: %w", e)
err = errors.Join(err, e)
}
if exporter.operationDurationMetric, e = otelconv.NewSDKExporterOperationDuration(m); e != nil {
e = fmt.Errorf("failed to create operation duration metric: %w", e)
err = errors.Join(err, e)
}
var err error
exporter.inst, err = observ.NewInstrumentation(counter.NextExporterID())
return exporter, err
}
@@ -91,107 +49,15 @@ type Exporter struct {
stoppedMu sync.RWMutex
stopped bool
selfObservabilityEnabled bool
selfObservabilityAttrs []attribute.KeyValue // selfObservability common attributes
selfObservabilitySetOpt metric.MeasurementOption
spanInflightMetric otelconv.SDKExporterSpanInflight
spanExportedMetric otelconv.SDKExporterSpanExported
operationDurationMetric otelconv.SDKExporterOperationDuration
inst *observ.Instrumentation
}
var (
measureAttrsPool = sync.Pool{
New: func() any {
// "component.name" + "component.type" + "error.type"
const n = 1 + 1 + 1
s := make([]attribute.KeyValue, 0, n)
// Return a pointer to a slice instead of a slice itself
// to avoid allocations on every call.
return &s
},
}
addOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.AddOption, 0, n)
return &o
},
}
recordOptPool = &sync.Pool{
New: func() any {
const n = 1 // WithAttributeSet
o := make([]metric.RecordOption, 0, n)
return &o
},
}
)
// ExportSpans writes spans in json format to stdout.
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
var success int64
if e.selfObservabilityEnabled {
count := int64(len(spans))
addOpt := addOptPool.Get().(*[]metric.AddOption)
defer func() {
*addOpt = (*addOpt)[:0]
addOptPool.Put(addOpt)
}()
*addOpt = append(*addOpt, e.selfObservabilitySetOpt)
e.spanInflightMetric.Inst().Add(ctx, count, *addOpt...)
defer func(starting time.Time) {
e.spanInflightMetric.Inst().Add(ctx, -count, *addOpt...)
// Record the success and duration of the operation.
//
// Do not exclude 0 values, as they are valid and indicate no spans
// were exported which is meaningful for certain aggregations.
e.spanExportedMetric.Inst().Add(ctx, success, *addOpt...)
mOpt := e.selfObservabilitySetOpt
if err != nil {
// additional attributes for self-observability,
// only spanExportedMetric and operationDurationMetric are supported.
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
defer func() {
*attrs = (*attrs)[:0] // reset the slice for reuse
measureAttrsPool.Put(attrs)
}()
*attrs = append(*attrs, e.selfObservabilityAttrs...)
*attrs = append(*attrs, semconv.ErrorType(err))
// Do not inefficiently make a copy of attrs by using
// WithAttributes instead of WithAttributeSet.
set := attribute.NewSet(*attrs...)
mOpt = metric.WithAttributeSet(set)
// Reset addOpt with new attribute set.
*addOpt = append((*addOpt)[:0], mOpt)
e.spanExportedMetric.Inst().Add(
ctx,
count-success,
*addOpt...,
)
}
recordOpt := recordOptPool.Get().(*[]metric.RecordOption)
defer func() {
*recordOpt = (*recordOpt)[:0]
recordOptPool.Put(recordOpt)
}()
*recordOpt = append(*recordOpt, mOpt)
e.operationDurationMetric.Inst().Record(
ctx,
time.Since(starting).Seconds(),
*recordOpt...,
)
}(time.Now())
if e.inst != nil {
op := e.inst.ExportSpans(ctx, len(spans))
defer func() { op.End(success, err) }()
}
if err := ctx.Err(); err != nil {
+96
View File
@@ -0,0 +1,96 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package errorhandler provides the global error handler for OpenTelemetry.
//
// This package has no OTel dependencies, allowing it to be imported by any
// package in the module without creating import cycles.
package errorhandler // import "go.opentelemetry.io/otel/internal/errorhandler"
import (
"errors"
"log"
"sync"
"sync/atomic"
)
// ErrorHandler handles irremediable events.
type ErrorHandler interface {
// Handle handles any error deemed irremediable by an OpenTelemetry
// component.
Handle(error)
}
type ErrDelegator struct {
delegate atomic.Pointer[ErrorHandler]
}
// Compile-time check that delegator implements ErrorHandler.
var _ ErrorHandler = (*ErrDelegator)(nil)
func (d *ErrDelegator) Handle(err error) {
if eh := d.delegate.Load(); eh != nil {
(*eh).Handle(err)
return
}
log.Print(err)
}
// setDelegate sets the ErrorHandler delegate.
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
d.delegate.Store(&eh)
}
type errorHandlerHolder struct {
eh ErrorHandler
}
var (
globalErrorHandler = defaultErrorHandler()
delegateErrorHandlerOnce sync.Once
)
// GetErrorHandler returns the global ErrorHandler instance.
//
// The default ErrorHandler instance returned will log all errors to STDERR
// until an override ErrorHandler is set with SetErrorHandler. All
// ErrorHandler returned prior to this will automatically forward errors to
// the set instance instead of logging.
//
// Subsequent calls to SetErrorHandler after the first will not forward errors
// to the new ErrorHandler for prior returned instances.
func GetErrorHandler() ErrorHandler {
return globalErrorHandler.Load().(errorHandlerHolder).eh
}
// SetErrorHandler sets the global ErrorHandler to h.
//
// The first time this is called all ErrorHandler previously returned from
// GetErrorHandler will send errors to h instead of the default logging
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
// delegate errors to h.
func SetErrorHandler(h ErrorHandler) {
current := GetErrorHandler()
if _, cOk := current.(*ErrDelegator); cOk {
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
// Do not assign to the delegate of the default ErrDelegator to be
// itself.
log.Print(errors.New("no ErrorHandler delegate configured"), " ErrorHandler remains its current value.")
return
}
}
delegateErrorHandlerOnce.Do(func() {
if def, ok := current.(*ErrDelegator); ok {
def.setDelegate(h)
}
})
globalErrorHandler.Store(errorHandlerHolder{eh: h})
}
func defaultErrorHandler() *atomic.Value {
v := &atomic.Value{}
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
return v
}
+7 -27
View File
@@ -5,33 +5,13 @@
package global // import "go.opentelemetry.io/otel/internal/global"
import (
"log"
"sync/atomic"
"go.opentelemetry.io/otel/internal/errorhandler"
)
// ErrorHandler handles irremediable events.
type ErrorHandler interface {
// Handle handles any error deemed irremediable by an OpenTelemetry
// component.
Handle(error)
}
// ErrorHandler is an alias for errorhandler.ErrorHandler, kept for backward
// compatibility with existing callers of internal/global.
type ErrorHandler = errorhandler.ErrorHandler
type ErrDelegator struct {
delegate atomic.Pointer[ErrorHandler]
}
// Compile-time check that delegator implements ErrorHandler.
var _ ErrorHandler = (*ErrDelegator)(nil)
func (d *ErrDelegator) Handle(err error) {
if eh := d.delegate.Load(); eh != nil {
(*eh).Handle(err)
return
}
log.Print(err)
}
// setDelegate sets the ErrorHandler delegate.
func (d *ErrDelegator) setDelegate(eh ErrorHandler) {
d.delegate.Store(&eh)
}
// ErrDelegator is an alias for errorhandler.ErrDelegator, kept for backward
// compatibility with existing callers of internal/global.
type ErrDelegator = errorhandler.ErrDelegator
+3 -33
View File
@@ -8,16 +8,13 @@ import (
"sync"
"sync/atomic"
"go.opentelemetry.io/otel/internal/errorhandler"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
type (
errorHandlerHolder struct {
eh ErrorHandler
}
tracerProviderHolder struct {
tp trace.TracerProvider
}
@@ -32,12 +29,10 @@ type (
)
var (
globalErrorHandler = defaultErrorHandler()
globalTracer = defaultTracerValue()
globalPropagators = defaultPropagatorsValue()
globalMeterProvider = defaultMeterProvider()
delegateErrorHandlerOnce sync.Once
delegateTraceOnce sync.Once
delegateTextMapPropagatorOnce sync.Once
delegateMeterOnce sync.Once
@@ -53,7 +48,7 @@ var (
// Subsequent calls to SetErrorHandler after the first will not forward errors
// to the new ErrorHandler for prior returned instances.
func GetErrorHandler() ErrorHandler {
return globalErrorHandler.Load().(errorHandlerHolder).eh
return errorhandler.GetErrorHandler()
}
// SetErrorHandler sets the global ErrorHandler to h.
@@ -63,26 +58,7 @@ func GetErrorHandler() ErrorHandler {
// ErrorHandler. Subsequent calls will set the global ErrorHandler, but not
// delegate errors to h.
func SetErrorHandler(h ErrorHandler) {
current := GetErrorHandler()
if _, cOk := current.(*ErrDelegator); cOk {
if _, ehOk := h.(*ErrDelegator); ehOk && current == h {
// Do not assign to the delegate of the default ErrDelegator to be
// itself.
Error(
errors.New("no ErrorHandler delegate configured"),
"ErrorHandler remains its current value.",
)
return
}
}
delegateErrorHandlerOnce.Do(func() {
if def, ok := current.(*ErrDelegator); ok {
def.setDelegate(h)
}
})
globalErrorHandler.Store(errorHandlerHolder{eh: h})
errorhandler.SetErrorHandler(h)
}
// TracerProvider is the internal implementation for global.TracerProvider.
@@ -174,12 +150,6 @@ func SetMeterProvider(mp metric.MeterProvider) {
globalMeterProvider.Store(meterProviderHolder{mp: mp})
}
func defaultErrorHandler() *atomic.Value {
v := &atomic.Value{}
v.Store(errorHandlerHolder{eh: &ErrDelegator{}})
return v
}
func defaultTracerValue() *atomic.Value {
v := &atomic.Value{}
v.Store(tracerProviderHolder{tp: &tracerProvider{}})
+3
View File
@@ -211,6 +211,9 @@ type Float64Observer interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Observe(value float64, options ...ObserveOption)
}
+3
View File
@@ -210,6 +210,9 @@ type Int64Observer interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Observe(value int64, options ...ObserveOption)
}
+53 -1
View File
@@ -30,6 +30,9 @@ type MeterProvider interface {
//
// If the name is empty, then an implementation defined default name will
// be used instead.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Meter(name string, opts ...MeterOption) Meter
}
@@ -51,6 +54,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64Counter(name string, options ...Int64CounterOption) (Int64Counter, error)
// Int64UpDownCounter returns a new Int64UpDownCounter instrument
@@ -61,6 +67,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64UpDownCounter(name string, options ...Int64UpDownCounterOption) (Int64UpDownCounter, error)
// Int64Histogram returns a new Int64Histogram instrument identified by
@@ -71,6 +80,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64Histogram(name string, options ...Int64HistogramOption) (Int64Histogram, error)
// Int64Gauge returns a new Int64Gauge instrument identified by name and
@@ -80,6 +92,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64Gauge(name string, options ...Int64GaugeOption) (Int64Gauge, error)
// Int64ObservableCounter returns a new Int64ObservableCounter identified
@@ -95,6 +110,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64ObservableCounter(name string, options ...Int64ObservableCounterOption) (Int64ObservableCounter, error)
// Int64ObservableUpDownCounter returns a new Int64ObservableUpDownCounter
@@ -110,6 +128,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64ObservableUpDownCounter(
name string,
options ...Int64ObservableUpDownCounterOption,
@@ -128,6 +149,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Int64ObservableGauge(name string, options ...Int64ObservableGaugeOption) (Int64ObservableGauge, error)
// Float64Counter returns a new Float64Counter instrument identified by
@@ -148,6 +172,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64UpDownCounter(name string, options ...Float64UpDownCounterOption) (Float64UpDownCounter, error)
// Float64Histogram returns a new Float64Histogram instrument identified by
@@ -158,6 +185,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64Histogram(name string, options ...Float64HistogramOption) (Float64Histogram, error)
// Float64Gauge returns a new Float64Gauge instrument identified by name and
@@ -167,6 +197,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64Gauge(name string, options ...Float64GaugeOption) (Float64Gauge, error)
// Float64ObservableCounter returns a new Float64ObservableCounter
@@ -182,6 +215,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64ObservableCounter(name string, options ...Float64ObservableCounterOption) (Float64ObservableCounter, error)
// Float64ObservableUpDownCounter returns a new
@@ -197,6 +233,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64ObservableUpDownCounter(
name string,
options ...Float64ObservableUpDownCounterOption,
@@ -215,6 +254,9 @@ type Meter interface {
// The name needs to conform to the OpenTelemetry instrument name syntax.
// See the Instrument Name section of the package documentation for more
// information.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Float64ObservableGauge(name string, options ...Float64ObservableGaugeOption) (Float64ObservableGauge, error)
// RegisterCallback registers f to be called during the collection of a
@@ -229,6 +271,9 @@ type Meter interface {
// If no instruments are passed, f should not be registered nor called
// during collection.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
//
// The function f needs to be concurrent safe.
RegisterCallback(f Callback, instruments ...Observable) (Registration, error)
}
@@ -263,9 +308,15 @@ type Observer interface {
embedded.Observer
// ObserveFloat64 records the float64 value for obsrv.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
ObserveFloat64(obsrv Float64Observable, value float64, opts ...ObserveOption)
// ObserveInt64 records the int64 value for obsrv.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
ObserveInt64(obsrv Int64Observable, value int64, opts ...ObserveOption)
}
@@ -283,6 +334,7 @@ type Registration interface {
// Unregister removes the callback registration from a Meter.
//
// This method needs to be idempotent and concurrent safe.
// Implementations of this method need to be idempotent and safe for a user
// to call concurrently.
Unregister() error
}
+24
View File
@@ -24,12 +24,18 @@ type Float64Counter interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Add(ctx context.Context, incr float64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -83,12 +89,18 @@ type Float64UpDownCounter interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Add(ctx context.Context, incr float64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -142,12 +154,18 @@ type Float64Histogram interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Record(ctx context.Context, incr float64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -206,12 +224,18 @@ type Float64Gauge interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Record(ctx context.Context, value float64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
+24
View File
@@ -24,12 +24,18 @@ type Int64Counter interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Add(ctx context.Context, incr int64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -83,12 +89,18 @@ type Int64UpDownCounter interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Add(ctx context.Context, incr int64, options ...AddOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -142,12 +154,18 @@ type Int64Histogram interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Record(ctx context.Context, incr int64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
@@ -206,12 +224,18 @@ type Int64Gauge interface {
//
// Use the WithAttributeSet (or, if performance is not a concern,
// the WithAttributes) option to include measurement attributes.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Record(ctx context.Context, value int64, options ...RecordOption)
// Enabled reports whether the instrument will process measurements for the given context.
//
// This function can be used in places where measuring an instrument
// would result in computationally expensive operations.
//
// Implementations of this method need to be safe for a user to call
// concurrently.
Enabled(context.Context) bool
}
+22 -2
View File
@@ -7,9 +7,16 @@ import (
"context"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/internal/errorhandler"
)
const baggageHeader = "baggage"
const (
baggageHeader = "baggage"
// W3C Baggage specification limits.
// https://www.w3.org/TR/baggage/#limits
maxMembers = 64
)
// Baggage is a propagator that supports the W3C Baggage format.
//
@@ -50,6 +57,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
bag, err := baggage.Parse(bStr)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
}
if bag.Len() == 0 {
return parent
}
return baggage.ContextWithBaggage(parent, bag)
@@ -60,17 +70,27 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
if len(bVals) == 0 {
return parent
}
var members []baggage.Member
for _, bStr := range bVals {
currBag, err := baggage.Parse(bStr)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
}
if currBag.Len() == 0 {
continue
}
members = append(members, currBag.Members()...)
if len(members) >= maxMembers {
break
}
}
b, err := baggage.New(members...)
if err != nil || b.Len() == 0 {
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
}
if b.Len() == 0 {
return parent
}
return baggage.ContextWithBaggage(parent, b)
+6 -7
View File
@@ -46,8 +46,8 @@ func (TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) {
carrier.Set(tracestateHeader, ts)
}
// Clear all flags other than the trace-context supported sampling bit.
flags := sc.TraceFlags() & trace.FlagsSampled
// Preserve only the spec-defined flags: sampled (0x01) and random (0x02).
flags := sc.TraceFlags() & (trace.FlagsSampled | trace.FlagsRandom)
var sb strings.Builder
sb.Grow(2 + 32 + 16 + 2 + 3)
@@ -104,14 +104,13 @@ func (TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
if !extractPart(opts[:], &h, 2) {
return trace.SpanContext{}
}
if version == 0 && (h != "" || opts[0] > 2) {
// version 0 not allow extra
// version 0 not allow other flag
if version == 0 && (h != "" || opts[0] > 3) {
// version 0 does not allow extra fields or reserved flag bits.
return trace.SpanContext{}
}
// Clear all flags other than the trace-context supported sampling bit.
scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled // nolint:gosec // slice size already checked.
scc.TraceFlags = trace.TraceFlags(opts[0]) & //nolint:gosec // slice size already checked.
(trace.FlagsSampled | trace.FlagsRandom)
// Ignore the error returned here. Failure to parse tracestate MUST NOT
// affect the parsing of traceparent according to the W3C tracecontext
+1 -1
View File
@@ -1 +1 @@
codespell==2.4.1
codespell==2.4.2
@@ -16,8 +16,8 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
const (
+4 -2
View File
@@ -15,7 +15,7 @@ import (
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/sdk/metric/internal/observ"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
// Default periodic reader timing.
@@ -107,7 +107,9 @@ func WithInterval(d time.Duration) PeriodicReaderOption {
// exporter. That is left to the user to accomplish.
func NewPeriodicReader(exporter Exporter, options ...PeriodicReaderOption) *PeriodicReader {
conf := newPeriodicReaderConfig(options)
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel( //nolint:gosec // cancel called during PeriodicReader shutdown.
context.Background(),
)
r := &PeriodicReader{
interval: conf.interval,
timeout: conf.timeout,
+1 -1
View File
@@ -5,5 +5,5 @@ package metric // import "go.opentelemetry.io/otel/sdk/metric"
// version is the current release version of the metric SDK in use.
func version() string {
return "1.40.0"
return "1.42.0"
}
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
type (
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"os"
"regexp"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
type containerIDProvider func() (string, error)
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
const (
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"errors"
"strings"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
type hostIDProvider func() (string, error)
+1 -1
View File
@@ -8,7 +8,7 @@ package resource // import "go.opentelemetry.io/otel/sdk/resource"
import "os"
func readFile(filename string) (string, error) {
b, err := os.ReadFile(filename)
b, err := os.ReadFile(filename) // nolint:gosec // false positive
if err != nil {
return "", err
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strings"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
type osDescriptionProvider func() (string, error)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"path/filepath"
"runtime"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)
type (
+2 -4
View File
@@ -123,12 +123,10 @@ func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorO
otel.Handle(err)
}
bsp.stopWait.Add(1)
go func() {
defer bsp.stopWait.Done()
bsp.stopWait.Go(func() {
bsp.processQueue()
bsp.drainQueue()
}()
})
return bsp
}
@@ -13,8 +13,8 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
const (
@@ -13,8 +13,8 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
)
var measureAttrsPool = sync.Pool{
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/internal/x"
"go.opentelemetry.io/otel/semconv/v1.39.0/otelconv"
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
"go.opentelemetry.io/otel/trace"
)
+4 -4
View File
@@ -5,6 +5,7 @@ package trace // import "go.opentelemetry.io/otel/sdk/trace"
import (
"context"
"errors"
"fmt"
"sync"
"sync/atomic"
@@ -262,6 +263,7 @@ func (p *TracerProvider) ForceFlush(ctx context.Context) error {
return nil
}
var err error
for _, sps := range spss {
select {
case <-ctx.Done():
@@ -269,11 +271,9 @@ func (p *TracerProvider) ForceFlush(ctx context.Context) error {
default:
}
if err := sps.sp.ForceFlush(ctx); err != nil {
return err
}
err = errors.Join(err, sps.sp.ForceFlush(ctx))
}
return nil
return err
}
// Shutdown shuts down TracerProvider. All registered span processors are shut down
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/embedded"
)
+1 -1
View File
@@ -6,5 +6,5 @@ package sdk // import "go.opentelemetry.io/otel/sdk"
// Version is the current release version of the OpenTelemetry SDK in use.
func Version() string {
return "1.40.0"
return "1.42.0"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-78
View File
@@ -1,78 +0,0 @@
<!-- Generated. DO NOT MODIFY. -->
# Migration from v1.38.0 to v1.39.0
The `go.opentelemetry.io/otel/semconv/v1.39.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.38.0` with the following exceptions.
## Removed
The following declarations have been removed.
Refer to the [OpenTelemetry Semantic Conventions documentation] for deprecation instructions.
If the type is not listed in the documentation as deprecated, it has been removed in this version due to lack of applicability or use.
If you use any of these non-deprecated declarations in your Go application, please [open an issue] describing your use-case.
- `LinuxMemorySlabStateKey`
- `LinuxMemorySlabStateReclaimable`
- `LinuxMemorySlabStateUnreclaimable`
- `PeerService`
- `PeerServiceKey`
- `RPCConnectRPCErrorCodeAborted`
- `RPCConnectRPCErrorCodeAlreadyExists`
- `RPCConnectRPCErrorCodeCancelled`
- `RPCConnectRPCErrorCodeDataLoss`
- `RPCConnectRPCErrorCodeDeadlineExceeded`
- `RPCConnectRPCErrorCodeFailedPrecondition`
- `RPCConnectRPCErrorCodeInternal`
- `RPCConnectRPCErrorCodeInvalidArgument`
- `RPCConnectRPCErrorCodeKey`
- `RPCConnectRPCErrorCodeNotFound`
- `RPCConnectRPCErrorCodeOutOfRange`
- `RPCConnectRPCErrorCodePermissionDenied`
- `RPCConnectRPCErrorCodeResourceExhausted`
- `RPCConnectRPCErrorCodeUnauthenticated`
- `RPCConnectRPCErrorCodeUnavailable`
- `RPCConnectRPCErrorCodeUnimplemented`
- `RPCConnectRPCErrorCodeUnknown`
- `RPCConnectRPCRequestMetadata`
- `RPCConnectRPCResponseMetadata`
- `RPCGRPCRequestMetadata`
- `RPCGRPCResponseMetadata`
- `RPCGRPCStatusCodeAborted`
- `RPCGRPCStatusCodeAlreadyExists`
- `RPCGRPCStatusCodeCancelled`
- `RPCGRPCStatusCodeDataLoss`
- `RPCGRPCStatusCodeDeadlineExceeded`
- `RPCGRPCStatusCodeFailedPrecondition`
- `RPCGRPCStatusCodeInternal`
- `RPCGRPCStatusCodeInvalidArgument`
- `RPCGRPCStatusCodeKey`
- `RPCGRPCStatusCodeNotFound`
- `RPCGRPCStatusCodeOk`
- `RPCGRPCStatusCodeOutOfRange`
- `RPCGRPCStatusCodePermissionDenied`
- `RPCGRPCStatusCodeResourceExhausted`
- `RPCGRPCStatusCodeUnauthenticated`
- `RPCGRPCStatusCodeUnavailable`
- `RPCGRPCStatusCodeUnimplemented`
- `RPCGRPCStatusCodeUnknown`
- `RPCJSONRPCErrorCode`
- `RPCJSONRPCErrorCodeKey`
- `RPCJSONRPCErrorMessage`
- `RPCJSONRPCErrorMessageKey`
- `RPCJSONRPCRequestID`
- `RPCJSONRPCRequestIDKey`
- `RPCJSONRPCVersion`
- `RPCJSONRPCVersionKey`
- `RPCService`
- `RPCServiceKey`
- `RPCSystemApacheDubbo`
- `RPCSystemConnectRPC`
- `RPCSystemDotnetWcf`
- `RPCSystemGRPC`
- `RPCSystemJSONRPC`
- `RPCSystemJavaRmi`
- `RPCSystemKey`
- `RPCSystemOncRPC`
[OpenTelemetry Semantic Conventions documentation]: https://github.com/open-telemetry/semantic-conventions
[open an issue]: https://github.com/open-telemetry/opentelemetry-go/issues/new?template=Blank+issue
-3
View File
@@ -1,3 +0,0 @@
# Semconv v1.39.0
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/semconv/v1.39.0)](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.39.0)
+27
View File
@@ -0,0 +1,27 @@
<!-- Generated. DO NOT MODIFY. -->
# Migration from v1.39.0 to v1.40.0
The `go.opentelemetry.io/otel/semconv/v1.40.0` package should be a drop-in replacement for `go.opentelemetry.io/otel/semconv/v1.39.0` with the following exceptions.
## Removed
The following declarations have been removed.
Refer to the [OpenTelemetry Semantic Conventions documentation] for deprecation instructions.
If the type is not listed in the documentation as deprecated, it has been removed in this version due to lack of applicability or use.
If you use any of these non-deprecated declarations in your Go application, please [open an issue] describing your use-case.
- `ErrorMessage`
- `ErrorMessageKey`
- `RPCMessageCompressedSize`
- `RPCMessageCompressedSizeKey`
- `RPCMessageID`
- `RPCMessageIDKey`
- `RPCMessageTypeKey`
- `RPCMessageTypeReceived`
- `RPCMessageTypeSent`
- `RPCMessageUncompressedSize`
- `RPCMessageUncompressedSizeKey`
[OpenTelemetry Semantic Conventions documentation]: https://github.com/open-telemetry/semantic-conventions
[open an issue]: https://github.com/open-telemetry/opentelemetry-go/issues/new?template=Blank+issue
+3
View File
@@ -0,0 +1,3 @@
# Semconv v1.40.0
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/semconv/v1.40.0)](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.40.0)
@@ -4,6 +4,6 @@
// Package semconv implements OpenTelemetry semantic conventions.
//
// OpenTelemetry semantic conventions are agreed standardized naming
// patterns for OpenTelemetry things. This package represents the v1.39.0
// patterns for OpenTelemetry things. This package represents the v1.40.0
// version of the OpenTelemetry semantic conventions.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
@@ -1,9 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
import (
"errors"
"reflect"
"go.opentelemetry.io/otel/attribute"
@@ -14,12 +15,14 @@ import (
// If err is nil, the returned attribute has the default value
// [ErrorTypeOther].
//
// If err's type has the method
// If err or one of the errors in its chain has the method
//
// ErrorType() string
//
// then the returned attribute has the value of err.ErrorType(). Otherwise, the
// returned attribute has a value derived from the concrete type of err.
// the returned attribute has that method's return value. If multiple errors in
// the chain implement this method, the value from the first match found by
// [errors.As] is used. Otherwise, the returned attribute has a value derived
// from the concrete type of err.
//
// The key of the returned attribute is [ErrorTypeKey].
func ErrorType(err error) attribute.KeyValue {
@@ -33,8 +36,15 @@ func ErrorType(err error) attribute.KeyValue {
func errorType(err error) string {
var s string
if et, ok := err.(interface{ ErrorType() string }); ok {
// Prioritize the ErrorType method if available.
// Fast path: check the top-level error first.
s = et.ErrorType()
} else {
// Fallback: search the error chain for an ErrorType method.
var et interface{ ErrorType() string }
if errors.As(err, &et) {
// Prioritize the ErrorType method if available.
s = et.ErrorType()
}
}
if s == "" {
// Fallback to reflection if the ErrorType method is not supported or
@@ -1,7 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
const (
// ExceptionEventName is the name of the Span event representing an exception.
@@ -67,6 +67,8 @@ var (
RequestMethodPut RequestMethodAttr = "PUT"
// RequestMethodTrace is the TRACE method.
RequestMethodTrace RequestMethodAttr = "TRACE"
// RequestMethodQuery is the QUERY method.
RequestMethodQuery RequestMethodAttr = "QUERY"
// RequestMethodOther is the any HTTP method that the instrumentation has no
// prior knowledge of.
RequestMethodOther RequestMethodAttr = "_OTHER"
@@ -158,7 +160,10 @@ func (m ClientActiveRequests) Add(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64UpDownCounter.Add(ctx, incr)
m.Int64UpDownCounter.Add(ctx, incr, metric.WithAttributes(
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -172,7 +177,7 @@ func (m ClientActiveRequests) Add(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
)...,
@@ -298,7 +303,10 @@ func (m ClientConnectionDuration) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Float64Histogram.Record(ctx, val)
m.Float64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -312,7 +320,7 @@ func (m ClientConnectionDuration) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
)...,
@@ -441,7 +449,11 @@ func (m ClientOpenConnections) Add(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64UpDownCounter.Add(ctx, incr)
m.Int64UpDownCounter.Add(ctx, incr, metric.WithAttributes(
attribute.String("http.connection.state", string(connectionState)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -455,7 +467,7 @@ func (m ClientOpenConnections) Add(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.connection.state", string(connectionState)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
@@ -590,7 +602,11 @@ func (m ClientRequestBodySize) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64Histogram.Record(ctx, val)
m.Int64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -604,7 +620,7 @@ func (m ClientRequestBodySize) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
@@ -766,7 +782,11 @@ func (m ClientRequestDuration) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Float64Histogram.Record(ctx, val)
m.Float64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -780,7 +800,7 @@ func (m ClientRequestDuration) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
@@ -942,7 +962,11 @@ func (m ClientResponseBodySize) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64Histogram.Record(ctx, val)
m.Int64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
))
return
}
@@ -956,7 +980,7 @@ func (m ClientResponseBodySize) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("server.address", serverAddress),
attribute.Int("server.port", serverPort),
@@ -1116,7 +1140,10 @@ func (m ServerActiveRequests) Add(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64UpDownCounter.Add(ctx, incr)
m.Int64UpDownCounter.Add(ctx, incr, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
))
return
}
@@ -1130,7 +1157,7 @@ func (m ServerActiveRequests) Add(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
)...,
@@ -1253,7 +1280,10 @@ func (m ServerRequestBodySize) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64Histogram.Record(ctx, val)
m.Int64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
))
return
}
@@ -1267,7 +1297,7 @@ func (m ServerRequestBodySize) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
)...,
@@ -1318,8 +1348,9 @@ func (ServerRequestBodySize) AttrResponseStatusCode(val int) attribute.KeyValue
}
// AttrRoute returns an optional attribute for the "http.route" semantic
// convention. It represents the matched route, that is, the path template in the
// format used by the respective server framework.
// convention. It represents the matched route template for the request. This
// MUST be low-cardinality and include all static path segments, with dynamic
// path segments represented with placeholders.
func (ServerRequestBodySize) AttrRoute(val string) attribute.KeyValue {
return attribute.String("http.route", val)
}
@@ -1436,7 +1467,10 @@ func (m ServerRequestDuration) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Float64Histogram.Record(ctx, val)
m.Float64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
))
return
}
@@ -1450,7 +1484,7 @@ func (m ServerRequestDuration) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
)...,
@@ -1494,8 +1528,9 @@ func (ServerRequestDuration) AttrResponseStatusCode(val int) attribute.KeyValue
}
// AttrRoute returns an optional attribute for the "http.route" semantic
// convention. It represents the matched route, that is, the path template in the
// format used by the respective server framework.
// convention. It represents the matched route template for the request. This
// MUST be low-cardinality and include all static path segments, with dynamic
// path segments represented with placeholders.
func (ServerRequestDuration) AttrRoute(val string) attribute.KeyValue {
return attribute.String("http.route", val)
}
@@ -1619,7 +1654,10 @@ func (m ServerResponseBodySize) Record(
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Int64Histogram.Record(ctx, val)
m.Int64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
))
return
}
@@ -1633,7 +1671,7 @@ func (m ServerResponseBodySize) Record(
*o,
metric.WithAttributes(
append(
attrs,
attrs[:len(attrs):len(attrs)],
attribute.String("http.request.method", string(requestMethod)),
attribute.String("url.scheme", urlScheme),
)...,
@@ -1684,8 +1722,9 @@ func (ServerResponseBodySize) AttrResponseStatusCode(val int) attribute.KeyValue
}
// AttrRoute returns an optional attribute for the "http.route" semantic
// convention. It represents the matched route, that is, the path template in the
// format used by the respective server framework.
// convention. It represents the matched route template for the request. This
// MUST be low-cardinality and include all static path segments, with dynamic
// path segments represented with placeholders.
func (ServerResponseBodySize) AttrRoute(val string) attribute.KeyValue {
return attribute.String("http.route", val)
}
+360
View File
@@ -0,0 +1,360 @@
// Code generated from semantic convention specification. DO NOT EDIT.
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package rpcconv provides types and functionality for OpenTelemetry semantic
// conventions in the "rpc" namespace.
package rpcconv
import (
"context"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
)
var (
addOptPool = &sync.Pool{New: func() any { return &[]metric.AddOption{} }}
recOptPool = &sync.Pool{New: func() any { return &[]metric.RecordOption{} }}
)
// ErrorTypeAttr is an attribute conforming to the error.type semantic
// conventions. It represents the describes a class of error the operation ended
// with.
type ErrorTypeAttr string
var (
// ErrorTypeOther is a fallback error value to be used when the instrumentation
// doesn't define a custom value.
ErrorTypeOther ErrorTypeAttr = "_OTHER"
)
// SystemNameAttr is an attribute conforming to the rpc.system.name semantic
// conventions. It represents the Remote Procedure Call (RPC) system.
type SystemNameAttr string
var (
// SystemNameGRPC is the [gRPC].
//
// [gRPC]: https://grpc.io/
SystemNameGRPC SystemNameAttr = "grpc"
// SystemNameDubbo is the [Apache Dubbo].
//
// [Apache Dubbo]: https://dubbo.apache.org/
SystemNameDubbo SystemNameAttr = "dubbo"
// SystemNameConnectrpc is the [Connect RPC].
//
// [Connect RPC]: https://connectrpc.com/
SystemNameConnectrpc SystemNameAttr = "connectrpc"
// SystemNameJSONRPC is the [JSON-RPC].
//
// [JSON-RPC]: https://www.jsonrpc.org/
SystemNameJSONRPC SystemNameAttr = "jsonrpc"
)
// ClientCallDuration is an instrument used to record metric values conforming to
// the "rpc.client.call.duration" semantic conventions. It represents the
// measures the duration of an outgoing Remote Procedure Call (RPC).
type ClientCallDuration struct {
metric.Float64Histogram
}
var newClientCallDurationOpts = []metric.Float64HistogramOption{
metric.WithDescription("Measures the duration of an outgoing Remote Procedure Call (RPC)."),
metric.WithUnit("s"),
}
// NewClientCallDuration returns a new ClientCallDuration instrument.
func NewClientCallDuration(
m metric.Meter,
opt ...metric.Float64HistogramOption,
) (ClientCallDuration, error) {
// Check if the meter is nil.
if m == nil {
return ClientCallDuration{noop.Float64Histogram{}}, nil
}
if len(opt) == 0 {
opt = newClientCallDurationOpts
} else {
opt = append(opt, newClientCallDurationOpts...)
}
i, err := m.Float64Histogram(
"rpc.client.call.duration",
opt...,
)
if err != nil {
return ClientCallDuration{noop.Float64Histogram{}}, err
}
return ClientCallDuration{i}, nil
}
// Inst returns the underlying metric instrument.
func (m ClientCallDuration) Inst() metric.Float64Histogram {
return m.Float64Histogram
}
// Name returns the semantic convention name of the instrument.
func (ClientCallDuration) Name() string {
return "rpc.client.call.duration"
}
// Unit returns the semantic convention unit of the instrument
func (ClientCallDuration) Unit() string {
return "s"
}
// Description returns the semantic convention description of the instrument
func (ClientCallDuration) Description() string {
return "Measures the duration of an outgoing Remote Procedure Call (RPC)."
}
// Record records val to the current distribution for attrs.
//
// The systemName is the the Remote Procedure Call (RPC) system.
//
// All additional attrs passed are included in the recorded value.
//
// When this metric is reported alongside an RPC client span, the metric value
// SHOULD be the same as the RPC client span duration.
func (m ClientCallDuration) Record(
ctx context.Context,
val float64,
systemName SystemNameAttr,
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Float64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("rpc.system.name", string(systemName)),
))
return
}
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
*o = (*o)[:0]
recOptPool.Put(o)
}()
*o = append(
*o,
metric.WithAttributes(
append(
attrs[:len(attrs):len(attrs)],
attribute.String("rpc.system.name", string(systemName)),
)...,
),
)
m.Float64Histogram.Record(ctx, val, *o...)
}
// RecordSet records val to the current distribution for set.
//
// When this metric is reported alongside an RPC client span, the metric value
// SHOULD be the same as the RPC client span duration.
func (m ClientCallDuration) RecordSet(ctx context.Context, val float64, set attribute.Set) {
if set.Len() == 0 {
m.Float64Histogram.Record(ctx, val)
return
}
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
*o = (*o)[:0]
recOptPool.Put(o)
}()
*o = append(*o, metric.WithAttributeSet(set))
m.Float64Histogram.Record(ctx, val, *o...)
}
// AttrErrorType returns an optional attribute for the "error.type" semantic
// convention. It represents the describes a class of error the operation ended
// with.
func (ClientCallDuration) AttrErrorType(val ErrorTypeAttr) attribute.KeyValue {
return attribute.String("error.type", string(val))
}
// AttrMethod returns an optional attribute for the "rpc.method" semantic
// convention. It represents the fully-qualified logical name of the method from
// the RPC interface perspective.
func (ClientCallDuration) AttrMethod(val string) attribute.KeyValue {
return attribute.String("rpc.method", val)
}
// AttrResponseStatusCode returns an optional attribute for the
// "rpc.response.status_code" semantic convention. It represents the status code
// of the RPC returned by the RPC server or generated by the client.
func (ClientCallDuration) AttrResponseStatusCode(val string) attribute.KeyValue {
return attribute.String("rpc.response.status_code", val)
}
// AttrServerAddress returns an optional attribute for the "server.address"
// semantic convention. It represents a string identifying a group of RPC server
// instances request is sent to.
func (ClientCallDuration) AttrServerAddress(val string) attribute.KeyValue {
return attribute.String("server.address", val)
}
// AttrServerPort returns an optional attribute for the "server.port" semantic
// convention. It represents the server port number.
func (ClientCallDuration) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
// ServerCallDuration is an instrument used to record metric values conforming to
// the "rpc.server.call.duration" semantic conventions. It represents the
// measures the duration of an incoming Remote Procedure Call (RPC).
type ServerCallDuration struct {
metric.Float64Histogram
}
var newServerCallDurationOpts = []metric.Float64HistogramOption{
metric.WithDescription("Measures the duration of an incoming Remote Procedure Call (RPC)."),
metric.WithUnit("s"),
}
// NewServerCallDuration returns a new ServerCallDuration instrument.
func NewServerCallDuration(
m metric.Meter,
opt ...metric.Float64HistogramOption,
) (ServerCallDuration, error) {
// Check if the meter is nil.
if m == nil {
return ServerCallDuration{noop.Float64Histogram{}}, nil
}
if len(opt) == 0 {
opt = newServerCallDurationOpts
} else {
opt = append(opt, newServerCallDurationOpts...)
}
i, err := m.Float64Histogram(
"rpc.server.call.duration",
opt...,
)
if err != nil {
return ServerCallDuration{noop.Float64Histogram{}}, err
}
return ServerCallDuration{i}, nil
}
// Inst returns the underlying metric instrument.
func (m ServerCallDuration) Inst() metric.Float64Histogram {
return m.Float64Histogram
}
// Name returns the semantic convention name of the instrument.
func (ServerCallDuration) Name() string {
return "rpc.server.call.duration"
}
// Unit returns the semantic convention unit of the instrument
func (ServerCallDuration) Unit() string {
return "s"
}
// Description returns the semantic convention description of the instrument
func (ServerCallDuration) Description() string {
return "Measures the duration of an incoming Remote Procedure Call (RPC)."
}
// Record records val to the current distribution for attrs.
//
// The systemName is the the Remote Procedure Call (RPC) system.
//
// All additional attrs passed are included in the recorded value.
//
// When this metric is reported alongside an RPC server span, the metric value
// SHOULD be the same as the RPC server span duration.
func (m ServerCallDuration) Record(
ctx context.Context,
val float64,
systemName SystemNameAttr,
attrs ...attribute.KeyValue,
) {
if len(attrs) == 0 {
m.Float64Histogram.Record(ctx, val, metric.WithAttributes(
attribute.String("rpc.system.name", string(systemName)),
))
return
}
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
*o = (*o)[:0]
recOptPool.Put(o)
}()
*o = append(
*o,
metric.WithAttributes(
append(
attrs[:len(attrs):len(attrs)],
attribute.String("rpc.system.name", string(systemName)),
)...,
),
)
m.Float64Histogram.Record(ctx, val, *o...)
}
// RecordSet records val to the current distribution for set.
//
// When this metric is reported alongside an RPC server span, the metric value
// SHOULD be the same as the RPC server span duration.
func (m ServerCallDuration) RecordSet(ctx context.Context, val float64, set attribute.Set) {
if set.Len() == 0 {
m.Float64Histogram.Record(ctx, val)
return
}
o := recOptPool.Get().(*[]metric.RecordOption)
defer func() {
*o = (*o)[:0]
recOptPool.Put(o)
}()
*o = append(*o, metric.WithAttributeSet(set))
m.Float64Histogram.Record(ctx, val, *o...)
}
// AttrErrorType returns an optional attribute for the "error.type" semantic
// convention. It represents the describes a class of error the operation ended
// with.
func (ServerCallDuration) AttrErrorType(val ErrorTypeAttr) attribute.KeyValue {
return attribute.String("error.type", string(val))
}
// AttrMethod returns an optional attribute for the "rpc.method" semantic
// convention. It represents the fully-qualified logical name of the method from
// the RPC interface perspective.
func (ServerCallDuration) AttrMethod(val string) attribute.KeyValue {
return attribute.String("rpc.method", val)
}
// AttrResponseStatusCode returns an optional attribute for the
// "rpc.response.status_code" semantic convention. It represents the status code
// of the RPC returned by the RPC server or generated by the client.
func (ServerCallDuration) AttrResponseStatusCode(val string) attribute.KeyValue {
return attribute.String("rpc.response.status_code", val)
}
// AttrServerAddress returns an optional attribute for the "server.address"
// semantic convention. It represents a string identifying a group of RPC server
// instances request is sent to.
func (ServerCallDuration) AttrServerAddress(val string) attribute.KeyValue {
return attribute.String("server.address", val)
}
// AttrServerPort returns an optional attribute for the "server.port" semantic
// convention. It represents the server port number.
func (ServerCallDuration) AttrServerPort(val int) attribute.KeyValue {
return attribute.Int("server.port", val)
}
@@ -1,9 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package semconv // import "go.opentelemetry.io/otel/semconv/v1.39.0"
package semconv // import "go.opentelemetry.io/otel/semconv/v1.40.0"
// SchemaURL is the schema URL that matches the version of the semantic conventions
// that this package defines. Semconv packages starting from v1.4.0 must declare
// non-empty schema URL in the form https://opentelemetry.io/schemas/<version>
const SchemaURL = "https://opentelemetry.io/schemas/1.39.0"
const SchemaURL = "https://opentelemetry.io/schemas/1.40.0"
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
"go.opentelemetry.io/otel/trace/embedded"
"go.opentelemetry.io/otel/trace/internal/telemetry"
)
+5
View File
@@ -12,6 +12,11 @@ const (
// with the sampling bit set means the span is sampled.
FlagsSampled = TraceFlags(0x01)
// FlagsRandom is a bitmask with the random trace ID flag set. When
// set, it signals that the trace ID was generated randomly with at
// least 56 bits of randomness (W3C Trace Context Level 2).
FlagsRandom = TraceFlags(0x02)
errInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase"
errInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32"
+6 -3
View File
@@ -61,7 +61,10 @@ func checkValue(val string) bool {
func checkKeyRemain(key string) bool {
// ( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
for _, v := range key {
if isAlphaNum(byte(v)) {
if v > 127 {
return false
}
if isAlphaNumASCII(v) {
continue
}
switch v {
@@ -89,7 +92,7 @@ func checkKeyPart(key string, n int) bool {
return ret && checkKeyRemain(key[1:])
}
func isAlphaNum(c byte) bool {
func isAlphaNumASCII[T rune | byte](c T) bool {
if c >= 'a' && c <= 'z' {
return true
}
@@ -105,7 +108,7 @@ func checkKeyTenant(key string, n int) bool {
if key == "" {
return false
}
return isAlphaNum(key[0]) && len(key[1:]) <= n && checkKeyRemain(key[1:])
return isAlphaNumASCII(key[0]) && len(key[1:]) <= n && checkKeyRemain(key[1:])
}
// based on the W3C Trace Context specification
+1 -1
View File
@@ -5,5 +5,5 @@ package otel // import "go.opentelemetry.io/otel"
// Version is the current release version of OpenTelemetry in use.
func Version() string {
return "1.40.0"
return "1.42.0"
}
+4 -4
View File
@@ -3,7 +3,7 @@
module-sets:
stable-v1:
version: v1.40.0
version: v1.42.0
modules:
- go.opentelemetry.io/otel
- go.opentelemetry.io/otel/bridge/opencensus
@@ -22,11 +22,11 @@ module-sets:
- go.opentelemetry.io/otel/sdk/metric
- go.opentelemetry.io/otel/trace
experimental-metrics:
version: v0.62.0
version: v0.64.0
modules:
- go.opentelemetry.io/otel/exporters/prometheus
experimental-logs:
version: v0.16.0
version: v0.18.0
modules:
- go.opentelemetry.io/otel/log
- go.opentelemetry.io/otel/log/logtest
@@ -36,7 +36,7 @@ module-sets:
- go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
- go.opentelemetry.io/otel/exporters/stdout/stdoutlog
experimental-schema:
version: v0.0.14
version: v0.0.16
modules:
- go.opentelemetry.io/otel/schema
excluded-modules: