You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.5 KiB
Go
134 lines
3.5 KiB
Go
4 years ago
|
package tracing
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
|
||
|
"go.opentelemetry.io/otel/attribute"
|
||
|
"go.opentelemetry.io/otel/codes"
|
||
|
"go.opentelemetry.io/otel/propagation"
|
||
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||
|
"go.opentelemetry.io/otel/trace"
|
||
|
)
|
||
|
|
||
|
// StartSpan starts a new span as a child of the span in context.
|
||
|
// If there is no span in context then this is a no-op.
|
||
|
func StartSpan(ctx context.Context, operationName string, opts ...trace.SpanStartOption) (trace.Span, context.Context) {
|
||
|
parent := trace.SpanFromContext(ctx)
|
||
|
tracer := trace.NewNoopTracerProvider().Tracer("")
|
||
|
if parent != nil && parent.SpanContext().IsValid() {
|
||
|
tracer = parent.TracerProvider().Tracer("")
|
||
|
}
|
||
|
ctx, span := tracer.Start(ctx, operationName, opts...)
|
||
|
return span, ctx
|
||
|
}
|
||
|
|
||
|
// FinishWithError finalizes the span and sets the error if one is passed
|
||
|
func FinishWithError(span trace.Span, err error) {
|
||
|
if err != nil {
|
||
|
span.RecordError(err)
|
||
|
if _, ok := err.(interface {
|
||
|
Cause() error
|
||
|
}); ok {
|
||
|
span.SetAttributes(attribute.String(string(semconv.ExceptionStacktraceKey), fmt.Sprintf("%+v", err)))
|
||
|
}
|
||
|
span.SetStatus(codes.Error, err.Error())
|
||
|
}
|
||
|
span.End()
|
||
|
}
|
||
|
|
||
|
// ContextWithSpanFromContext sets the tracing span of a context from other
|
||
|
// context if one is not already set. Alternative would be
|
||
|
// context.WithoutCancel() that would copy the context but reset ctx.Done
|
||
|
func ContextWithSpanFromContext(ctx, ctx2 context.Context) context.Context {
|
||
|
// if already is a span then noop
|
||
|
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
|
||
|
return ctx
|
||
|
}
|
||
|
if span := trace.SpanFromContext(ctx2); span != nil && span.SpanContext().IsValid() {
|
||
|
return trace.ContextWithSpan(ctx, span)
|
||
|
}
|
||
|
return ctx
|
||
|
}
|
||
|
|
||
|
var DefaultTransport http.RoundTripper = &Transport{
|
||
|
RoundTripper: NewTransport(http.DefaultTransport),
|
||
|
}
|
||
|
|
||
|
var DefaultClient = &http.Client{
|
||
|
Transport: DefaultTransport,
|
||
|
}
|
||
|
|
||
|
var propagators = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
|
||
|
|
||
|
type Transport struct {
|
||
|
http.RoundTripper
|
||
|
}
|
||
|
|
||
|
func NewTransport(rt http.RoundTripper) http.RoundTripper {
|
||
|
// TODO: switch to otelhttp. needs upstream updates to avoid transport-global tracer
|
||
|
return &Transport{
|
||
|
RoundTripper: rt,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||
|
span := trace.SpanFromContext(req.Context())
|
||
|
if !span.SpanContext().IsValid() { // no tracer connected with either request or transport
|
||
|
return t.RoundTripper.RoundTrip(req)
|
||
|
}
|
||
|
|
||
|
ctx, span := span.TracerProvider().Tracer("").Start(req.Context(), req.Method)
|
||
|
|
||
|
req = req.WithContext(ctx)
|
||
|
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(req)...)
|
||
|
propagators.Inject(ctx, propagation.HeaderCarrier(req.Header))
|
||
|
|
||
|
resp, err := t.RoundTripper.RoundTrip(req)
|
||
|
if err != nil {
|
||
|
span.RecordError(err)
|
||
|
span.End()
|
||
|
return resp, err
|
||
|
}
|
||
|
|
||
|
span.SetAttributes(semconv.HTTPAttributesFromHTTPStatusCode(resp.StatusCode)...)
|
||
|
span.SetStatus(semconv.SpanStatusFromHTTPStatusCode(resp.StatusCode))
|
||
|
|
||
|
if req.Method == "HEAD" {
|
||
|
span.End()
|
||
|
} else {
|
||
|
resp.Body = &wrappedBody{ctx: ctx, span: span, body: resp.Body}
|
||
|
}
|
||
|
|
||
|
return resp, 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(err)
|
||
|
}
|
||
|
return n, err
|
||
|
}
|
||
|
|
||
|
func (wb *wrappedBody) Close() error {
|
||
|
wb.span.End()
|
||
|
return wb.body.Close()
|
||
|
}
|