1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-14 10:13:10 +02:00
opentelemetry-go/plugin/othttp/transport.go

133 lines
3.6 KiB
Go
Raw Normal View History

// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package othttp
import (
"context"
"io"
"net/http"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace"
"google.golang.org/grpc/codes"
)
// Transport implements the http.RoundTripper interface and wraps
// outbound HTTP(S) requests with a span.
type Transport struct {
rt http.RoundTripper
tracer trace.Tracer
propagators propagation.Propagators
spanStartOptions []trace.StartOption
filters []Filter
spanNameFormatter func(string, *http.Request) string
}
var _ http.RoundTripper = &Transport{}
// NewTransport wraps the provided http.RoundTripper with one that
// starts a span and injects the span context into the outbound request headers.
func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
t := Transport{
rt: base,
}
defaultOpts := []Option{
WithTracer(global.Tracer("go.opentelemetry.io/plugin/othttp")),
WithPropagators(global.Propagators()),
WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
WithSpanNameFormatter(defaultTransportFormatter),
}
c := NewConfig(append(defaultOpts, opts...)...)
t.configure(c)
return &t
}
func (t *Transport) configure(c *Config) {
t.tracer = c.Tracer
t.propagators = c.Propagators
t.spanStartOptions = c.SpanStartOptions
t.filters = c.Filters
t.spanNameFormatter = c.SpanNameFormatter
}
func defaultTransportFormatter(_ string, r *http.Request) string {
return r.Method
}
// RoundTrip creates a Span and propagates its context via the provided request's headers
// before handing the request to the configured base RoundTripper. The created span will
// end when the response body is closed or when a read from the body returns io.EOF.
func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
for _, f := range t.filters {
if !f(r) {
// Simply pass through to the base RoundTripper if a filter rejects the request
return t.rt.RoundTrip(r)
}
}
opts := append([]trace.StartOption{}, t.spanStartOptions...) // start with the configured options
ctx, span := t.tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
r = r.WithContext(ctx)
setBasicAttributes(span, r)
propagation.InjectHTTP(ctx, t.propagators, r.Header)
res, err := t.rt.RoundTrip(r)
if err != nil {
span.RecordError(ctx, err, trace.WithErrorStatus(codes.Internal))
span.End()
return res, err
}
span.SetAttributes(StatusCodeKey.Int(res.StatusCode))
res.Body = &wrappedBody{ctx: ctx, span: span, body: res.Body}
return res, err
}
type wrappedBody struct {
ctx context.Context
span trace.Span
body io.ReadCloser
}
var _ io.ReadCloser = &wrappedBody{}
func (wb *wrappedBody) Read(b []byte) (int, error) {
n, err := wb.body.Read(b)
switch err {
case nil:
// nothing to do here but fall through to the return
case io.EOF:
wb.span.End()
default:
wb.span.RecordError(wb.ctx, err, trace.WithErrorStatus(codes.Internal))
}
return n, err
}
func (wb *wrappedBody) Close() error {
wb.span.End()
return wb.body.Close()
}