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
20func New(ctx context.Context, family, title string) (*Trace, context.Context) {
21 tracer := GetOpenTracer(ctx, nil)
22 span, ctx := opentracing.StartSpanFromContextWithTracer(ctx,
23 tracer,
24 family,
25 opentracing.Tag{Key: "title", Value: title},
26 )
27
28 tr := nettrace.New(family, title)
29 trace := &Trace{span: span, trace: tr, family: family}
30 if parent := TraceFromContext(ctx); parent != nil {
31 tr.LazyPrintf("parent: %s", parent.family)
32 trace.family = parent.family + " > " + family
33 }
34 return trace, ContextWithTrace(ctx, trace)
35}
36
37// Trace is a combined version of golang.org/x/net/trace.Trace and
38// opentracing.Span. Use New to construct one.
39type Trace struct {
40 trace nettrace.Trace
41 span opentracing.Span
42 family string
43}
44
45// LazyPrintf evaluates its arguments with fmt.Sprintf each time the
46// /debug/requests page is rendered. Any memory referenced by a will be
47// pinned until the trace is finished and later discarded.
48func (t *Trace) LazyPrintf(format string, a ...any) {
49 t.span.LogFields(Printf("log", format, a...))
50 t.trace.LazyPrintf(format, a...)
51}
52
53func (t *Trace) LazyLog(x fmt.Stringer, sensitive bool) {
54 t.trace.LazyLog(x, sensitive)
55}
56
57// LogFields logs fields to the opentracing.Span
58// as well as the nettrace.Trace.
59func (t *Trace) LogFields(fields ...log.Field) {
60 t.span.LogFields(fields...)
61 t.trace.LazyLog(fieldsStringer(fields), false)
62}
63
64// SetError declares that this trace and span resulted in an error.
65func (t *Trace) SetError(err error) {
66 if err == nil {
67 return
68 }
69 t.trace.LazyPrintf("error: %v", err)
70 t.trace.SetError()
71 t.span.LogFields(log.Error(err))
72 ext.Error.Set(t.span, true)
73}
74
75// Finish declares that this trace and span is complete.
76// The trace should not be used after calling this method.
77func (t *Trace) Finish() {
78 t.trace.Finish()
79 t.span.Finish()
80}
81
82// Printf is an opentracing log.Field which is a LazyLogger. So the format
83// string will only be evaluated if the trace is collected. In the case of
84// net/trace, it will only be evaluated on page load.
85func Printf(key, f string, args ...any) log.Field {
86 return log.Lazy(func(fv log.Encoder) {
87 fv.EmitString(key, fmt.Sprintf(f, args...))
88 })
89}
90
91type traceContextKey string
92
93const traceKey = traceContextKey("trace")
94
95// ContextWithTrace returns a new context.Context that holds a reference to
96// trace's SpanContext.
97func ContextWithTrace(ctx context.Context, tr *Trace) context.Context {
98 ctx = opentracing.ContextWithSpan(ctx, tr.span)
99 ctx = context.WithValue(ctx, traceKey, tr)
100 return ctx
101}
102
103// TraceFromContext returns the Trace previously associated with ctx, or
104// nil if no such Trace could be found.
105func TraceFromContext(ctx context.Context) *Trace {
106 tr, _ := ctx.Value(traceKey).(*Trace)
107 return tr
108}
109
110// fieldsStringer lazily marshals a slice of log.Field into a string for
111// printing in net/trace.
112type fieldsStringer []log.Field
113
114func (fs fieldsStringer) String() string {
115 var e encoder
116 for _, f := range fs {
117 f.Marshal(&e)
118 }
119 return e.Builder.String()
120}
121
122// encoder is a log.Encoder used by fieldsStringer.
123type encoder struct {
124 strings.Builder
125 prefixNewline bool
126}
127
128func (e *encoder) EmitString(key, value string) {
129 if e.prefixNewline {
130 // most times encoder is used is for one field
131 e.Builder.WriteString("\n")
132 }
133 if !e.prefixNewline {
134 e.prefixNewline = true
135 }
136
137 e.Builder.Grow(len(key) + 1 + len(value))
138 e.Builder.WriteString(key)
139 e.Builder.WriteString(":")
140 e.Builder.WriteString(value)
141}
142
143func (e *encoder) EmitBool(key string, value bool) {
144 e.EmitString(key, strconv.FormatBool(value))
145}
146
147func (e *encoder) EmitInt(key string, value int) {
148 e.EmitString(key, strconv.Itoa(value))
149}
150
151func (e *encoder) EmitInt32(key string, value int32) {
152 e.EmitString(key, strconv.FormatInt(int64(value), 10))
153}
154
155func (e *encoder) EmitInt64(key string, value int64) {
156 e.EmitString(key, strconv.FormatInt(value, 10))
157}
158
159func (e *encoder) EmitUint32(key string, value uint32) {
160 e.EmitString(key, strconv.FormatUint(uint64(value), 10))
161}
162
163func (e *encoder) EmitUint64(key string, value uint64) {
164 e.EmitString(key, strconv.FormatUint(value, 10))
165}
166
167func (e *encoder) EmitFloat32(key string, value float32) {
168 e.EmitString(key, strconv.FormatFloat(float64(value), 'E', -1, 64))
169}
170
171func (e *encoder) EmitFloat64(key string, value float64) {
172 e.EmitString(key, strconv.FormatFloat(value, 'E', -1, 64))
173}
174
175func (e *encoder) EmitObject(key string, value any) {
176 e.EmitString(key, fmt.Sprintf("%+v", value))
177}
178
179func (e *encoder) EmitLazyLogger(value log.LazyLogger) {
180 value(e)
181}