// Package otlpresource builds the OpenTelemetry resource shared between the // tracing and logger packages so spans, metrics, and logs describe the same // process with the same attributes (service.name, process.pid, host.*, etc.). // Keep the detector list in sync across signals. package otlpresource import ( "context" "log/slog" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) // Options carries optional attributes that only some callers supply. // Zero values are skipped. type Options struct { ServiceVersion string // added as semconv.ServiceVersionKey Environment string // added as attribute "environment" } // New builds the shared OTel resource. Resource detection is best-effort: // any detector error is logged via the provided slog logger and a usable // resource is still returned. The returned error is currently always nil; the // signature is preserved so future callers can distinguish hard failures if // the contract changes. func New(ctx context.Context, log *slog.Logger, opts Options) (*resource.Resource, error) { detectors := []resource.Option{ resource.WithFromEnv(), // OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES resource.WithTelemetrySDK(), // telemetry.sdk.* resource.WithProcess(), // process.pid + process.executable.* + process.runtime.* resource.WithOS(), resource.WithContainer(), resource.WithHost(), } if opts.ServiceVersion != "" { detectors = append(detectors, resource.WithAttributes(semconv.ServiceVersionKey.String(opts.ServiceVersion))) } if opts.Environment != "" { detectors = append(detectors, resource.WithAttributes(attribute.String("environment", opts.Environment))) } // Detector failures (e.g. processOwnerDetector hitting user.Current() in // a non-cgo build with no /etc/passwd entry and unset $USER) must not be // fatal. Downgrade everything to a warning and fall back to an empty // resource if the SDK returns nil. res, err := resource.New(ctx, detectors...) if err != nil && log != nil { log.Warn("otel resource setup", "err", err) } if res == nil { res = resource.Empty() } return res, nil }