mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-14 10:13:10 +02:00
973b38607b
* Create basic othttp.Transport for simple client instrumentation * change props to propagators for clarity * Add othttp.Transport usage example Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
// 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()
|
|
}
|