Files
common/internal/otlpresource/resource.go
Ask Bjørn Hansen 988e07ade5 fix(tracing): treat resource detector errors as non-fatal
processOwnerDetector calls os/user.Current(), which fails in non-cgo
builds when the running UID has no /etc/passwd entry and $USER is unset
(common on hardened/distroless containers). It returns a plain error,
not ErrPartialResource, so the previous gate let the error escape and
SetupSDK aborted process startup.

Resource detection is documented as best-effort; downgrade any detector
error to a warning and fall back to an empty resource if the SDK returns
nil. The signature is preserved.
2026-05-02 22:00:09 -07:00

59 lines
2.2 KiB
Go

// 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
}