Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/common-go/tracing/tracing.go
2498 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package tracing
6
7
import (
8
"bytes"
9
"context"
10
"encoding/base64"
11
"fmt"
12
"io"
13
"os"
14
15
"github.com/opentracing/opentracing-go"
16
tracelog "github.com/opentracing/opentracing-go/log"
17
"github.com/sirupsen/logrus"
18
jaeger "github.com/uber/jaeger-client-go"
19
jaegercfg "github.com/uber/jaeger-client-go/config"
20
"google.golang.org/protobuf/encoding/protojson"
21
"google.golang.org/protobuf/proto"
22
23
"github.com/gitpod-io/gitpod/common-go/log"
24
"github.com/gitpod-io/gitpod/components/scrubber"
25
)
26
27
type tracingOptions struct {
28
prometheusReporter *PromReporter
29
}
30
31
// Option configures the tracing
32
type Option func(o *tracingOptions)
33
34
// WithPrometheusReporter enables the reporting of span durations as Prometheus histograms
35
func WithPrometheusReporter(p *PromReporter) Option {
36
return func(o *tracingOptions) {
37
o.prometheusReporter = p
38
}
39
}
40
41
// Init initializes tracing for this application
42
func Init(serviceName string, opts ...Option) io.Closer {
43
cfg, err := jaegercfg.FromEnv()
44
if err != nil {
45
log.WithError(err).Debug("cannot initialize Jaeger tracer from env")
46
return nil
47
}
48
49
cfg.Tags = append(cfg.Tags, opentracing.Tag{
50
Key: "service.build.commit",
51
Value: os.Getenv("GITPOD_BUILD_GIT_COMMIT"),
52
})
53
cfg.Tags = append(cfg.Tags, opentracing.Tag{
54
Key: "service.build.version",
55
Value: os.Getenv("GITPOD_BUILD_VERSION"),
56
})
57
58
reporter, err := cfg.Reporter.NewReporter(serviceName, nil, nil)
59
if err != nil {
60
log.WithError(err).Debug("cannot initialize Jaeger tracer from env")
61
return nil
62
}
63
64
var options tracingOptions
65
for _, opt := range opts {
66
opt(&options)
67
}
68
69
if options.prometheusReporter != nil {
70
promrep := options.prometheusReporter
71
err = promrep.RegisterMetrics()
72
if err != nil {
73
log.WithError(err).Debug("cannot register PrometheusReporter metrics - not using this reporter")
74
} else {
75
reporter = jaeger.NewCompositeReporter(reporter, promrep)
76
}
77
}
78
79
closer, err := cfg.InitGlobalTracer(serviceName, jaegercfg.Reporter(reporter))
80
if err != nil {
81
log.WithError(err).Debug("cannot initialize Jaeger tracer")
82
return nil
83
}
84
85
return closer
86
}
87
88
// FinishSpan reports an error if there is one and finishes the span
89
func FinishSpan(span opentracing.Span, err *error) {
90
if err != nil && *err != nil {
91
LogError(span, *err)
92
}
93
94
span.Finish()
95
}
96
97
// FromContext starts a new span from a context
98
func FromContext(ctx context.Context, name string) (opentracing.Span, context.Context) {
99
return opentracing.StartSpanFromContext(ctx, name)
100
}
101
102
// ApplyOWI sets the owner, workspace and instance tags on a span
103
func ApplyOWI(span opentracing.Span, owi logrus.Fields) {
104
for _, k := range []string{log.OwnerIDField, log.WorkspaceIDField, log.WorkspaceInstanceIDField, log.ProjectIDField, log.TeamIDField} {
105
val, ok := owi[k]
106
if !ok {
107
continue
108
}
109
110
span.SetTag(k, val)
111
}
112
}
113
114
// GetTraceID extracts the uber-trace-id from the context which encodes
115
// {trace-id}:{span-id}:{parent-span-id}:{flags}
116
func GetTraceID(span opentracing.Span) string {
117
var buf bytes.Buffer
118
err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.Binary, &buf)
119
if err != nil {
120
return ""
121
}
122
123
return base64.StdEncoding.EncodeToString(buf.Bytes())
124
}
125
126
// FromTraceID takes the output of GetTraceID and produces an OpenTracing span from it.
127
// If traceID is invalid, we return nil.
128
func FromTraceID(traceID string) opentracing.SpanContext {
129
if traceID == "" {
130
return nil
131
}
132
133
decoded, err := base64.StdEncoding.DecodeString(traceID)
134
if err != nil {
135
// we don't want to log here as this function will be called very often and if wsman is used without
136
// tracing, this would get rather spammy
137
return nil
138
}
139
140
spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.Binary, bytes.NewReader(decoded))
141
if err != nil {
142
// we don't want to log here as this function will be called very often and if wsman is used without
143
// tracing, this would get rather spammy
144
return nil
145
}
146
147
return spanCtx
148
}
149
150
// LogError logs an error and marks the span as errornous
151
func LogError(span opentracing.Span, err error) {
152
span.LogFields(tracelog.Error(err))
153
span.SetTag("error", true)
154
}
155
156
// LogRequestSafe logs the incoming request but redacts passwords and secrets
157
func LogRequestSafe(span opentracing.Span, req proto.Message) {
158
LogMessageSafe(span, "request", req)
159
}
160
161
// LogMessageSafe logs a grpc message but redacts passwords and secrets
162
func LogMessageSafe(span opentracing.Span, name string, req proto.Message) {
163
reqs, _ := protojson.Marshal(req)
164
safeReqs, err := scrubber.Default.JSON(reqs)
165
166
var msg string
167
if err != nil {
168
msg = fmt.Sprintf("cannot redact request: %v", err)
169
} else {
170
msg = string(safeReqs)
171
}
172
173
span.LogKV(name, msg)
174
}
175
176