// Copyright (C) 2019-2020 Zilliz. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distributed under the License // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express // or implied. See the License for the specific language governing permissions and limitations under the License. package trace import ( "context" "errors" "io" "runtime" "strings" "sync" slog "github.com/milvus-io/milvus/internal/log" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/log" "github.com/uber/jaeger-client-go" "github.com/uber/jaeger-client-go/config" "go.uber.org/zap" ) var tracingCloserMtx sync.Mutex var tracingCloser io.Closer // InitTracing init global trace from env. If not specified, use default config. func InitTracing(serviceName string) io.Closer { tracingCloserMtx.Lock() defer tracingCloserMtx.Unlock() if tracingCloser != nil { return tracingCloser } cfg := &config.Configuration{ ServiceName: serviceName, Sampler: &config.SamplerConfig{ Type: "const", Param: 0, }, } if true { cfg = initFromEnv(serviceName) } tracer, closer, err := cfg.NewTracer() tracingCloser = closer if err != nil { log.Error(err) tracingCloser = nil } opentracing.SetGlobalTracer(tracer) return tracingCloser } func initFromEnv(serviceName string) *config.Configuration { cfg, err := config.FromEnv() if err != nil { log.Error(err) return nil } cfg.ServiceName = serviceName return cfg } // StartSpanFromContext starts an opentracing span. The default operation name is // upper two call stacks of the function func StartSpanFromContext(ctx context.Context, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { return StartSpanFromContextWithSkip(ctx, 3, opts...) } // StartSpanFromContextWithSkip starts an opentracing span with call skip. The operation // name is upper @skip call stacks of the function func StartSpanFromContextWithSkip(ctx context.Context, skip int, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { if ctx == nil { return NoopSpan(), nil } var pcs [1]uintptr n := runtime.Callers(skip, pcs[:]) if n < 1 { span, ctx := opentracing.StartSpanFromContext(ctx, "unknown", opts...) span.LogFields(log.Error(errors.New("runtime.Callers failed"))) return span, ctx } frames := runtime.CallersFrames(pcs[:]) frame, _ := frames.Next() name := frame.Function if lastSlash := strings.LastIndexByte(name, '/'); lastSlash > 0 { name = name[lastSlash+1:] } if parent := opentracing.SpanFromContext(ctx); parent != nil { opts = append(opts, opentracing.ChildOf(parent.Context())) } span := opentracing.StartSpan(name, opts...) file, line := frame.File, frame.Line span.LogFields(log.String("filename", file), log.Int("line", line)) return span, opentracing.ContextWithSpan(ctx, span) } // StartSpanFromContextWithOperationName starts an opentracing span with specific operation name. // And will log print the current call line number and file name. func StartSpanFromContextWithOperationName(ctx context.Context, operationName string, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { return StartSpanFromContextWithOperationNameWithSkip(ctx, operationName, 3, opts...) } // StartSpanFromContextWithOperationNameWithSkip starts an opentracing span with specific operation name. // And will log print the current call line number and file name. func StartSpanFromContextWithOperationNameWithSkip(ctx context.Context, operationName string, skip int, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { if ctx == nil { return NoopSpan(), nil } var pcs [1]uintptr n := runtime.Callers(skip, pcs[:]) if n < 1 { span, ctx := opentracing.StartSpanFromContext(ctx, operationName, opts...) span.LogFields(log.Error(errors.New("runtime.Callers failed"))) return span, ctx } frames := runtime.CallersFrames(pcs[:]) frame, _ := frames.Next() file, line := frame.File, frame.Line if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { opts = append(opts, opentracing.ChildOf(parentSpan.Context())) } span := opentracing.StartSpan(operationName, opts...) ctx = opentracing.ContextWithSpan(ctx, span) span.LogFields(log.String("filename", file), log.Int("line", line)) return span, ctx } // LogError is a method to log error with span. func LogError(span opentracing.Span, err error) { if err == nil { return } // Get caller frame. var pcs [1]uintptr n := runtime.Callers(2, pcs[:]) if n < 1 { span.LogFields(log.Error(err)) span.LogFields(log.Error(errors.New("runtime.Callers failed"))) slog.Warn("trace log error failed", zap.Error(err)) } frames := runtime.CallersFrames(pcs[:]) frame, _ := frames.Next() file, line := frame.File, frame.Line span.LogFields(log.String("filename", file), log.Int("line", line), log.Error(err)) } // InfoFromSpan is a method return span details. func InfoFromSpan(span opentracing.Span) (traceID string, sampled, found bool) { if span != nil { if spanContext, ok := span.Context().(jaeger.SpanContext); ok { traceID = spanContext.TraceID().String() sampled = spanContext.IsSampled() return traceID, sampled, true } } return "", false, false } // InfoFromContext is a method return details of span associated with context. func InfoFromContext(ctx context.Context) (traceID string, sampled, found bool) { if ctx != nil { if span := opentracing.SpanFromContext(ctx); span != nil { return InfoFromSpan(span) } } return "", false, false } // InjectContextToPulsarMsgProperties is a method inject span to pulsr message. func InjectContextToPulsarMsgProperties(sc opentracing.SpanContext, properties map[string]string) { tracer := opentracing.GlobalTracer() tracer.Inject(sc, opentracing.TextMap, PropertiesReaderWriter{properties}) } // PropertiesReaderWriter is for saving trce in pulsar msg properties. // Implement Set and ForeachKey methods. type PropertiesReaderWriter struct { PpMap map[string]string } // Set sets key, value to PpMap. func (ppRW PropertiesReaderWriter) Set(key, val string) { key = strings.ToLower(key) ppRW.PpMap[key] = val } // ForeachKey iterates each key value of PpMap. func (ppRW PropertiesReaderWriter) ForeachKey(handler func(key, val string) error) error { for k, val := range ppRW.PpMap { if err := handler(k, val); err != nil { return err } } return nil } // NoopSpan is a minimal span to reduce overhead. func NoopSpan() opentracing.Span { return opentracing.NoopTracer{}.StartSpan("Default-span") }