fork of https://github.com/sourcegraph/zoekt
1// Package trace provides a tracing API that in turn invokes both the `golang.org/x/net/trace` API
2// and creates an opentracing span if appropriate.
3//
4// This is similar to the github.com/sourcegraph/sourcegraph/internal/trace package in the main repo,
5// and it may make sense to factor both out into a common package at some point.
6package trace
7
8import (
9 "context"
10 "fmt"
11 "strconv"
12 "strings"
13
14 "github.com/opentracing/opentracing-go"
15 "github.com/opentracing/opentracing-go/ext"
16 "github.com/opentracing/opentracing-go/log"
17 nettrace "golang.org/x/net/trace"
18)
19
20// A Tracer for trace creation, parameterised over an
21// opentracing.Tracer. Use this if you don't want to use
22// the global tracer.
23type Tracer struct {
24 Tracer opentracing.Tracer
25}
26
27func New(ctx context.Context, family, title string) (*Trace, context.Context) {
28 tr := Tracer{Tracer: GetOpenTracer(ctx, nil)}
29 return tr.New(ctx, family, title)
30}
31
32// New returns a new Trace with the specified family and title.
33func (t Tracer) New(ctx context.Context, family, title string) (*Trace, context.Context) {
34 // In Zoekt child OpenTracing Spans don't really make much sense since all
35 // our spans are either middleware which just wrap an actual search, or the
36 // actual search. So we only create a new span if there is no parent.
37 parent := TraceFromContext(ctx)
38 var span opentracing.Span
39 if parent != nil {
40 span = parent.span
41 span.LogFields(log.String("child.family", family), log.String("child.title", title))
42 } else {
43 span, ctx = StartSpanFromContextWithTracer(
44 ctx,
45 t.Tracer,
46 family,
47 opentracing.Tag{Key: "title", Value: title},
48 )
49 }
50
51 tr := nettrace.New(family, title)
52 trace := &Trace{span: span, trace: tr, family: family}
53 if parent != nil {
54 tr.LazyPrintf("parent: %s", parent.family)
55 trace.family = parent.family + " > " + family
56 }
57
58 return trace, ContextWithTrace(ctx, trace)
59}
60
61// Trace is a combined version of golang.org/x/net/trace.Trace and
62// opentracing.Span. Use New to construct one.
63type Trace struct {
64 trace nettrace.Trace
65 span opentracing.Span
66 family string
67}
68
69// LazyPrintf evaluates its arguments with fmt.Sprintf each time the
70// /debug/requests page is rendered. Any memory referenced by a will be
71// pinned until the trace is finished and later discarded.
72func (t *Trace) LazyPrintf(format string, a ...interface{}) {
73 t.span.LogFields(Printf("log", format, a...))
74 t.trace.LazyPrintf(format, a...)
75}
76
77func (t *Trace) LazyLog(x fmt.Stringer, sensitive bool) {
78 t.trace.LazyLog(x, sensitive)
79}
80
81// LogFields logs fields to the opentracing.Span
82// as well as the nettrace.Trace.
83func (t *Trace) LogFields(fields ...log.Field) {
84 t.span.LogFields(fields...)
85 t.trace.LazyLog(fieldsStringer(fields), false)
86}
87
88// SetError declares that this trace and span resulted in an error.
89func (t *Trace) SetError(err error) {
90 if err == nil {
91 return
92 }
93 t.trace.LazyPrintf("error: %v", err)
94 t.trace.SetError()
95 t.span.LogFields(log.Error(err))
96 ext.Error.Set(t.span, true)
97}
98
99// Finish declares that this trace and span is complete.
100// The trace should not be used after calling this method.
101func (t *Trace) Finish() {
102 t.trace.Finish()
103 t.span.Finish()
104}
105
106// Printf is an opentracing log.Field which is a LazyLogger. So the format
107// string will only be evaluated if the trace is collected. In the case of
108// net/trace, it will only be evaluated on page load.
109func Printf(key, f string, args ...interface{}) log.Field {
110 return log.Lazy(func(fv log.Encoder) {
111 fv.EmitString(key, fmt.Sprintf(f, args...))
112 })
113}
114
115type traceContextKey string
116
117const traceKey = traceContextKey("trace")
118
119// ContextWithTrace returns a new context.Context that holds a reference to
120// trace's SpanContext.
121func ContextWithTrace(ctx context.Context, tr *Trace) context.Context {
122 ctx = opentracing.ContextWithSpan(ctx, tr.span)
123 ctx = context.WithValue(ctx, traceKey, tr)
124 return ctx
125}
126
127// TraceFromContext returns the Trace previously associated with ctx, or
128// nil if no such Trace could be found.
129func TraceFromContext(ctx context.Context) *Trace {
130 tr, _ := ctx.Value(traceKey).(*Trace)
131 return tr
132}
133
134// fieldsStringer lazily marshals a slice of log.Field into a string for
135// printing in net/trace.
136type fieldsStringer []log.Field
137
138func (fs fieldsStringer) String() string {
139 var e encoder
140 for _, f := range fs {
141 f.Marshal(&e)
142 }
143 return e.Builder.String()
144}
145
146// encoder is a log.Encoder used by fieldsStringer.
147type encoder struct {
148 strings.Builder
149 prefixNewline bool
150}
151
152func (e *encoder) EmitString(key, value string) {
153 if e.prefixNewline {
154 // most times encoder is used is for one field
155 e.Builder.WriteString("\n")
156 }
157 if !e.prefixNewline {
158 e.prefixNewline = true
159 }
160
161 e.Builder.Grow(len(key) + 1 + len(value))
162 e.Builder.WriteString(key)
163 e.Builder.WriteString(":")
164 e.Builder.WriteString(value)
165}
166
167func (e *encoder) EmitBool(key string, value bool) {
168 e.EmitString(key, strconv.FormatBool(value))
169}
170
171func (e *encoder) EmitInt(key string, value int) {
172 e.EmitString(key, strconv.Itoa(value))
173}
174
175func (e *encoder) EmitInt32(key string, value int32) {
176 e.EmitString(key, strconv.FormatInt(int64(value), 10))
177}
178func (e *encoder) EmitInt64(key string, value int64) {
179 e.EmitString(key, strconv.FormatInt(value, 10))
180}
181func (e *encoder) EmitUint32(key string, value uint32) {
182 e.EmitString(key, strconv.FormatUint(uint64(value), 10))
183}
184func (e *encoder) EmitUint64(key string, value uint64) {
185 e.EmitString(key, strconv.FormatUint(value, 10))
186}
187func (e *encoder) EmitFloat32(key string, value float32) {
188 e.EmitString(key, strconv.FormatFloat(float64(value), 'E', -1, 64))
189}
190func (e *encoder) EmitFloat64(key string, value float64) {
191 e.EmitString(key, strconv.FormatFloat(value, 'E', -1, 64))
192}
193func (e *encoder) EmitObject(key string, value interface{}) {
194 e.EmitString(key, fmt.Sprintf("%+v", value))
195}
196
197func (e *encoder) EmitLazyLogger(value log.LazyLogger) {
198 value(e)
199}