Path: blob/main/components/common-go/tracing/tracing.go
2498 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package tracing56import (7"bytes"8"context"9"encoding/base64"10"fmt"11"io"12"os"1314"github.com/opentracing/opentracing-go"15tracelog "github.com/opentracing/opentracing-go/log"16"github.com/sirupsen/logrus"17jaeger "github.com/uber/jaeger-client-go"18jaegercfg "github.com/uber/jaeger-client-go/config"19"google.golang.org/protobuf/encoding/protojson"20"google.golang.org/protobuf/proto"2122"github.com/gitpod-io/gitpod/common-go/log"23"github.com/gitpod-io/gitpod/components/scrubber"24)2526type tracingOptions struct {27prometheusReporter *PromReporter28}2930// Option configures the tracing31type Option func(o *tracingOptions)3233// WithPrometheusReporter enables the reporting of span durations as Prometheus histograms34func WithPrometheusReporter(p *PromReporter) Option {35return func(o *tracingOptions) {36o.prometheusReporter = p37}38}3940// Init initializes tracing for this application41func Init(serviceName string, opts ...Option) io.Closer {42cfg, err := jaegercfg.FromEnv()43if err != nil {44log.WithError(err).Debug("cannot initialize Jaeger tracer from env")45return nil46}4748cfg.Tags = append(cfg.Tags, opentracing.Tag{49Key: "service.build.commit",50Value: os.Getenv("GITPOD_BUILD_GIT_COMMIT"),51})52cfg.Tags = append(cfg.Tags, opentracing.Tag{53Key: "service.build.version",54Value: os.Getenv("GITPOD_BUILD_VERSION"),55})5657reporter, err := cfg.Reporter.NewReporter(serviceName, nil, nil)58if err != nil {59log.WithError(err).Debug("cannot initialize Jaeger tracer from env")60return nil61}6263var options tracingOptions64for _, opt := range opts {65opt(&options)66}6768if options.prometheusReporter != nil {69promrep := options.prometheusReporter70err = promrep.RegisterMetrics()71if err != nil {72log.WithError(err).Debug("cannot register PrometheusReporter metrics - not using this reporter")73} else {74reporter = jaeger.NewCompositeReporter(reporter, promrep)75}76}7778closer, err := cfg.InitGlobalTracer(serviceName, jaegercfg.Reporter(reporter))79if err != nil {80log.WithError(err).Debug("cannot initialize Jaeger tracer")81return nil82}8384return closer85}8687// FinishSpan reports an error if there is one and finishes the span88func FinishSpan(span opentracing.Span, err *error) {89if err != nil && *err != nil {90LogError(span, *err)91}9293span.Finish()94}9596// FromContext starts a new span from a context97func FromContext(ctx context.Context, name string) (opentracing.Span, context.Context) {98return opentracing.StartSpanFromContext(ctx, name)99}100101// ApplyOWI sets the owner, workspace and instance tags on a span102func ApplyOWI(span opentracing.Span, owi logrus.Fields) {103for _, k := range []string{log.OwnerIDField, log.WorkspaceIDField, log.WorkspaceInstanceIDField, log.ProjectIDField, log.TeamIDField} {104val, ok := owi[k]105if !ok {106continue107}108109span.SetTag(k, val)110}111}112113// GetTraceID extracts the uber-trace-id from the context which encodes114// {trace-id}:{span-id}:{parent-span-id}:{flags}115func GetTraceID(span opentracing.Span) string {116var buf bytes.Buffer117err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.Binary, &buf)118if err != nil {119return ""120}121122return base64.StdEncoding.EncodeToString(buf.Bytes())123}124125// FromTraceID takes the output of GetTraceID and produces an OpenTracing span from it.126// If traceID is invalid, we return nil.127func FromTraceID(traceID string) opentracing.SpanContext {128if traceID == "" {129return nil130}131132decoded, err := base64.StdEncoding.DecodeString(traceID)133if err != nil {134// we don't want to log here as this function will be called very often and if wsman is used without135// tracing, this would get rather spammy136return nil137}138139spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.Binary, bytes.NewReader(decoded))140if err != nil {141// we don't want to log here as this function will be called very often and if wsman is used without142// tracing, this would get rather spammy143return nil144}145146return spanCtx147}148149// LogError logs an error and marks the span as errornous150func LogError(span opentracing.Span, err error) {151span.LogFields(tracelog.Error(err))152span.SetTag("error", true)153}154155// LogRequestSafe logs the incoming request but redacts passwords and secrets156func LogRequestSafe(span opentracing.Span, req proto.Message) {157LogMessageSafe(span, "request", req)158}159160// LogMessageSafe logs a grpc message but redacts passwords and secrets161func LogMessageSafe(span opentracing.Span, name string, req proto.Message) {162reqs, _ := protojson.Marshal(req)163safeReqs, err := scrubber.Default.JSON(reqs)164165var msg string166if err != nil {167msg = fmt.Sprintf("cannot redact request: %v", err)168} else {169msg = string(safeReqs)170}171172span.LogKV(name, msg)173}174175176