package trace import ( "context" "fmt" "net/url" "sync" "github.com/zeromicro/go-zero/core/lang" "github.com/zeromicro/go-zero/core/logx" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/jaeger" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/zipkin" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) const ( kindJaeger = "jaeger" kindZipkin = "zipkin" kindOtlpGrpc = "otlpgrpc" kindOtlpHttp = "otlphttp" ) var ( agents = make(map[string]lang.PlaceholderType) lock sync.Mutex tp *sdktrace.TracerProvider ) // StartAgent starts an opentelemetry agent. func StartAgent(c Config) { if c.Disabled { return } lock.Lock() defer lock.Unlock() _, ok := agents[c.Endpoint] if ok { return } // if error happens, let later calls run. if err := startAgent(c); err != nil { return } agents[c.Endpoint] = lang.Placeholder } // StopAgent shuts down the span processors in the order they were registered. func StopAgent() { _ = tp.Shutdown(context.Background()) } func createExporter(c Config) (sdktrace.SpanExporter, error) { // Just support jaeger and zipkin now, more for later switch c.Batcher { case kindJaeger: u, _ := url.Parse(c.Endpoint) if u.Scheme == "udp" { return jaeger.New(jaeger.WithAgentEndpoint(jaeger.WithAgentHost(u.Hostname()), jaeger.WithAgentPort(u.Port()))) } return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint))) case kindZipkin: return zipkin.New(c.Endpoint) case kindOtlpGrpc: // Always treat trace exporter as optional component, so we use nonblock here, // otherwise this would slow down app start up even set a dial timeout here when // endpoint can not reach. // If the connection not dial success, the global otel ErrorHandler will catch error // when reporting data like other exporters. opts := []otlptracegrpc.Option{ otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(c.Endpoint), } if len(c.OtlpHeaders) > 0 { opts = append(opts, otlptracegrpc.WithHeaders(c.OtlpHeaders)) } return otlptracegrpc.New( context.Background(), opts..., ) case kindOtlpHttp: // Not support flexible configuration now. opts := []otlptracehttp.Option{ otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint(c.Endpoint), } if len(c.OtlpHeaders) > 0 { opts = append(opts, otlptracehttp.WithHeaders(c.OtlpHeaders)) } if len(c.OtlpHttpPath) > 0 { opts = append(opts, otlptracehttp.WithURLPath(c.OtlpHttpPath)) } return otlptracehttp.New( context.Background(), opts..., ) default: return nil, fmt.Errorf("unknown exporter: %s", c.Batcher) } } func startAgent(c Config) error { opts := []sdktrace.TracerProviderOption{ // Set the sampling rate based on the parent span to 100% sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))), // Record information about this application in a Resource. sdktrace.WithResource(resource.NewSchemaless(semconv.ServiceNameKey.String(c.Name))), } if len(c.Endpoint) > 0 { exp, err := createExporter(c) if err != nil { logx.Error(err) return err } // Always be sure to batch in production. opts = append(opts, sdktrace.WithBatcher(exp)) } tp = sdktrace.NewTracerProvider(opts...) otel.SetTracerProvider(tp) otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { logx.Errorf("[otel] error: %v", err) })) return nil }