buildkit/util/tracing/tracing.go

110 lines
2.8 KiB
Go

package tracing
import (
"context"
"fmt"
"io"
"net/http"
"github.com/opentracing-contrib/go-stdlib/nethttp"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
)
// 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.
// The difference from opentracing.StartSpanFromContext is that this method
// does not depend on global tracer.
func StartSpan(ctx context.Context, operationName string, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) {
parent := opentracing.SpanFromContext(ctx)
tracer := opentracing.Tracer(&opentracing.NoopTracer{})
if parent != nil {
tracer = parent.Tracer()
opts = append(opts, opentracing.ChildOf(parent.Context()))
}
span := tracer.StartSpan(operationName, opts...)
if parent != nil {
return span, opentracing.ContextWithSpan(ctx, span)
}
return span, ctx
}
// FinishWithError finalizes the span and sets the error if one is passed
func FinishWithError(span opentracing.Span, err error) {
if err != nil {
fields := []log.Field{
log.String("event", "error"),
log.String("message", err.Error()),
}
if _, ok := err.(interface {
Cause() error
}); ok {
fields = append(fields, log.String("stack", fmt.Sprintf("%+v", err)))
}
span.LogFields(fields...)
ext.Error.Set(span, true)
}
span.Finish()
}
// 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 := opentracing.SpanFromContext(ctx); span != nil {
return ctx
}
if span := opentracing.SpanFromContext(ctx2); span != nil {
return opentracing.ContextWithSpan(ctx, span)
}
return ctx
}
var DefaultTransport http.RoundTripper = &Transport{
RoundTripper: &nethttp.Transport{RoundTripper: http.DefaultTransport},
}
var DefaultClient = &http.Client{
Transport: DefaultTransport,
}
type Transport struct {
http.RoundTripper
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
span := opentracing.SpanFromContext(req.Context())
if span == nil { // no tracer connected with either request or transport
return t.RoundTripper.RoundTrip(req)
}
req, tracer := nethttp.TraceRequest(span.Tracer(), req)
resp, err := t.RoundTripper.RoundTrip(req)
if err != nil {
tracer.Finish()
return resp, err
}
if req.Method == "HEAD" {
tracer.Finish()
} else {
resp.Body = closeTracker{resp.Body, tracer.Finish}
}
return resp, err
}
type closeTracker struct {
io.ReadCloser
finish func()
}
func (c closeTracker) Close() error {
err := c.ReadCloser.Close()
c.finish()
return err
}