diff --git a/util/tracing/detect/detect.go b/util/tracing/detect/detect.go index 27c969180a63..cb94490e28c2 100644 --- a/util/tracing/detect/detect.go +++ b/util/tracing/detect/detect.go @@ -26,6 +26,12 @@ type detector struct { var ServiceName string var Recorder *TraceRecorder +// Resource is the resource used by the tracer provider. It may be set +// by an importing program before TracerProvider is first invoked to +// override the detected resource (e.g. to keep the semconv schema URL +// consistent with the program's own). +var Resource *resource.Resource + var detectors map[string]detector var once sync.Once var tp trace.TracerProvider @@ -97,13 +103,8 @@ func detect() error { // enable log with traceID when valid exporter bklog.EnableLogWithTraceID(true) - res, err := resource.Detect(context.Background(), serviceNameDetector{}) - if err != nil { - return err - } - res, err = resource.Merge(resource.Default(), res) - if err != nil { - return err + if Resource == nil { + Resource = detectResource() } sp := sdktrace.NewBatchSpanProcessor(exp) @@ -112,7 +113,7 @@ func detect() error { Recorder.flush = sp.ForceFlush } - sdktp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sp), sdktrace.WithResource(res)) + sdktp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sp), sdktrace.WithResource(Resource)) closers = append(closers, sdktp.Shutdown) exporter = exp diff --git a/util/tracing/detect/resource.go b/util/tracing/detect/resource.go new file mode 100644 index 000000000000..95e406fbc9fa --- /dev/null +++ b/util/tracing/detect/resource.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package detect + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +func detectResource() *resource.Resource { + res, err := resource.New(context.Background(), + resource.WithDetectors(serviceNameDetector{}), + resource.WithFromEnv(), + resource.WithDetectors(telemetrySDK{}), + ) + if err != nil { + otel.Handle(err) + } + return res +} + +type ( + telemetrySDK struct{} +) + +var ( + _ resource.Detector = telemetrySDK{} +) + +// Detect returns a *Resource that describes the OpenTelemetry SDK used. +func (telemetrySDK) Detect(context.Context) (*resource.Resource, error) { + return resource.NewWithAttributes( + semconv.SchemaURL, + semconv.TelemetrySDKName("opentelemetry"), + semconv.TelemetrySDKLanguageGo, + // otel.Version instead of sdk.Version: the root sdk package + // does not exist yet at the otel version pinned by this branch, + // and its builtin telemetrySDK detector reports otel.Version too. + semconv.TelemetrySDKVersion(otel.Version()), + ), nil +} diff --git a/util/tracing/detect/resource_test.go b/util/tracing/detect/resource_test.go new file mode 100644 index 000000000000..5327f5336f1b --- /dev/null +++ b/util/tracing/detect/resource_test.go @@ -0,0 +1,29 @@ +package detect + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" +) + +func TestResource(t *testing.T) { + prevHandler := otel.GetErrorHandler() + t.Cleanup(func() { + otel.SetErrorHandler(prevHandler) + }) + + var resourceErr error + otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { + resourceErr = err + })) + + res := detectResource() + + // Should not have an empty schema url. Only happens when + // there is a schema conflict. + require.NotEqual(t, "", res.SchemaURL()) + + // No error should have been invoked. + require.NoError(t, resourceErr) +}