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) { 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, parseErr := url.Parse(c.Endpoint) if parseErr != nil { return nil, fmt.Errorf("invalid jaeger endpoint: %s", 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. return otlptracegrpc.New( context.Background(), otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(c.Endpoint), ) case kindOtlpHttp: // Not support flexible configuration now. return otlptracehttp.New( context.Background(), otlptracehttp.WithInsecure(), otlptracehttp.WithEndpoint(c.Endpoint), ) 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 }